Agent-ready feedback loops for internal dogfooding. See it β report it β route it β fix it.
snapfeed is a feedback capture layer for teams where humans and agents both review software. A tester, designer, PM agent, QA agent, or design-review agent spots something in the product, opens the widget, sends one sentence, and snapfeed turns that moment into structured feedback with screenshot, URL, viewport, console errors, reporter identity, and build context.
Use it as the intake pipe for an agentic product loop: review agent finds the issue, snapfeed routes the payload, your orchestrator wakes the right coding agent, and the fix can move into test and deployment. Humans can use the same widget, but the architecture assumes the next reviewer might be Hermes, OpenClaw, Codex, Claude Code, OpenCode, a PM agent, or a QA/design agent.
Not an end-customer feedback widget. If you want a public "tell us what you think" form, use Canny. If you want your team and agents to file high-context bugs while they test β keep reading.
The story is bigger than filing a ticket: a human or agent reviewer spots an issue, snapfeed captures the context, your orchestrator routes it to the right coding agent, and the fix can move through test, PR, and deployment.
| Mode | For | Setup |
|---|---|---|
| π Cloud-relayed | Indie / hackathon / small startup | 5 min |
| π’ Self-hosted | Startup β mid-size | 30 min |
| π Air-gapped | Corp / regulated | 1-2 weeks (incl. security review) |
Same widget. Different backend topology. Pick based on what your IT will approve.
New here? Choose one path:
- Try it locally: run the 60-second quickstart below; feedback lands in
feedback.jsonl. - Wire your team: add one server-side destination such as Slack, GitHub, Linear, JIRA, or webhook.
- Ship it safely: follow the production checklist before enabling the widget beyond local/staging.
Per-persona quickstart guides (5 min β 1 hour, copy-paste runnable): see docs/quickstart/ for indie, startup, mid-size, corp, OSS-maintainer, and designer walkthroughs.
Most feedback tools assume a human PM reads the ticket later. snapfeed assumes you may already have a full agent architecture:
| Reviewer | What they inspect | Where snapfeed sends it |
|---|---|---|
| QA agent | broken flows, failed checks, regressions | GitHub Issue, Linear/JIRA bug, webhook to orchestrator |
| Design agent | layout, copy, accessibility, visual diffs | Slack/design channel, design QA queue, webhook |
| PM agent | acceptance criteria, launch blockers, prioritization | roadmap board, issue tracker, triage inbox |
| Human tester/designer | anything that feels wrong in the actual product | the same route, same payload, same audit trail |
The usual loop looks like this:
reviewer agent or human
β snapfeed widget / feedback API
β /api/feedback with screenshot + URL + metadata
β Slack/JIRA/Linear/GitHub/webhook
β orchestrator assigns coding agent
β fix PR β tests β deployment
snapfeed is not the orchestrator and does not auto-deploy by itself. It is the structured feedback handoff. Point webhookAdapter at Hermes, OpenClaw, your queue, or your own orchestration layer when you want feedback to trigger an agent run.
If you are an AI coding agent adding snapfeed to a repo, do this in order:
- Read
AGENTS.mdfor the deterministic install path. - Run
npm install snapfeedandnpx snapfeed init --yes. - Mount
<FeedbackProvider>in the app root and pass authenticateduserdata if available. - Configure one destination. For an agent loop, use
SNAPFEED_WEBHOOK_URLor a custom adapter that posts to your orchestrator. - Run
npx snapfeed doctor, thennpx snapfeed doctor --prodbefore enabling production. - Smoke test the handler:
curl -X POST http://localhost:3000/api/feedback \
-H 'Content-Type: application/json' \
-d '{"text":"agent smoke test","appName":"MyApp","pageUrl":"http://localhost:3000","pageName":"Home","timestamp":"2026-04-26T12:00:00Z"}'Success means the app accepted structured feedback. A real message/ticket/webhook delivery means your routing works. For production, verify the destination payload reaches the agent queue and that your coding agent can map the feedback to a repo, branch, test command, and deployment policy.
npm install snapfeed
npx snapfeed init --yesThen wrap your root layout (this is the one step the CLI can't do for you):
// app/layout.tsx (Next.js App Router) β or your equivalent root component
import { FeedbackProvider } from 'snapfeed'
export default function RootLayout({ children }) {
return (
<html>
<body>
<FeedbackProvider appName="My App">{children}</FeedbackProvider>
</body>
</html>
)
}For Next.js App Router, the FeedbackProvider needs to live in a 'use client' component β the CLI scaffolds app/snapfeed-client.tsx for you; just import it into your layout.
npm run devPress Ctrl+Shift+F (Cmd+Shift+F on Mac). Feedback dumps to ./feedback.jsonl and your browser console. No env vars, no adapters, no signup.
Wire a real destination β one env var, then restart npm run dev:
echo 'SNAPFEED_SLACK_WEBHOOK=<your Slack webhook URL>' >> .env.localNeed a Slack webhook URL? https://api.slack.com/messaging/webhooks (5 steps).
The auto-adapter detects SNAPFEED_* env vars and wires them. Note: only the SNAPFEED_-prefixed names are read; SLACK_WEBHOOK (without the prefix) is silently ignored.
Stuck? Run
npx snapfeed doctor. It prints a green/yellow/red checklist of your setup β install version, framework, destinations wired, env-var typos (with did-you-mean suggestions), handler file presence, and an optional--probe=<url>to check your dev server's/api/feedbackroute is reachable. Before shipping beyond local/staging, runnpx snapfeed doctor --prodto also check for explicitallowedOriginsandrateLimitguardrails. Always your first-stop debug command.
Design-review agent. It finishes reviewing a staging build and flags a confusing checkbox label on the payment step. snapfeed captures the screen, URL, viewport, build metadata, and the agent's note.
PM agent. It receives the routed item in Linear or JIRA, checks it against acceptance criteria, and decides whether it blocks launch or goes into backlog.
Coding agent. Your orchestrator turns the feedback into a repo task: branch, reproduce, fix, run tests, open PR. snapfeed supplies the context; your agent stack owns the remediation policy.
Human tester. A designer, PM, beta user, or employee can use the exact same flow. They do not need to know which agent owns checkout, which board to file in, or what metadata the engineer needs.
For indies, hackathon teams, small startups who want zero infra. Browser β widget β your /api/feedback route β server-side adapter (Slack webhook / GitHub API / Discord webhook). No snapfeed-operated relay. One npm install, one env var, restart.
// app/api/feedback/route.ts
import { createFeedbackHandler } from 'snapfeed/server/nextjs'
import { autoAdapters } from 'snapfeed/adapters'
export const POST = createFeedbackHandler({
adapters: autoAdapters(),
// Required before enabling the widget beyond local/staging:
allowedOrigins: ['https://staging.example.com'],
rateLimit: { max: 10, windowMs: 60_000 },
})For startups and mid-size teams that want their own database, their own LLM key, no third-party data path. v0.4 ships a Docker compose stack that boots a worker + MinIO (object store) + optional Ollama in one command:
cd docker
cp .env.example .env
docker compose upThen point the widget at http://<host>:8787/feedback. Add --profile llm to also start a local Ollama. See docker/README.md. Postgres-backed inbox + admin write-back are slated for v0.7.
For deployments where you'd rather host the worker yourself in your existing Node app:
// app/api/feedback/route.ts
import { createFeedbackHandler } from 'snapfeed/server/nextjs'
import { supabaseAdapter, slackAdapter } from 'snapfeed/adapters'
export const POST = createFeedbackHandler({
adapters: [
supabaseAdapter({
url: process.env.SUPABASE_URL!,
serviceKey: process.env.SUPABASE_SERVICE_ROLE_KEY!,
}),
slackAdapter({ webhookUrl: process.env.SLACK_WEBHOOK! }),
],
rateLimit: { max: 10, windowMs: 60_000 },
allowedOrigins: ['https://staging.myapp.com', /\.myapp\.com$/],
})For corporates and regulated industries where every new outbound domain needs a security review. v0.6 ships a self-hostable Docker stack: docker compose -f docker/docker-compose.yml up runs the worker + MinIO + optional Ollama (--profile llm) entirely inside your infrastructure. Pair with webhookAdapter pointed at your internal bug tracker, fileAuditLog to record every dispatch, and redactForLLM before any in-tenant LLM call. See docker/README.md for the install guide and SECURITY.md for the corporate review checklist. Image-digest pinning shipped in v0.6 (run ./docker/pin-digests.sh --apply); signed tarball + SSO/SAML for the admin app are slated for v0.7 (see SECURITY.md Β§Coming in later releases).
| Persona | Most likely destinations | Most likely mode |
|---|---|---|
| Indie / OSS maintainer | GitHub Issues, Discord, file | Cloud-relayed |
| Startup founder/PM | Slack, Linear, Sheet | Cloud-relayed β Self-hosted |
| Mid-size eng manager | Slack, JIRA, Postgres | Self-hosted |
| Corp eng / QA lead | JIRA, ServiceNow, MS Teams | Air-gapped |
| Designer (any team) | Whatever their team set up | n/a β they just press the hotkey |
import { FeedbackProvider } from 'snapfeed'
<FeedbackProvider appName="Checkout" hotkey="ctrl+shift+f">
{children}
</FeedbackProvider>| Prop | Type | Default | Description |
|---|---|---|---|
appName |
string |
"App" |
Shown in UI and in adapter notifications |
hotkey |
string |
"ctrl+shift+f" |
Format: "ctrl+shift+f", "meta+k", "ctrl+alt+b" |
position |
"bottom-right" | "bottom-left" | "top-right" | "top-left" |
"bottom-right" |
Floating trigger position |
theme |
"auto" | "light" | "dark" |
"auto" |
Color theme; auto follows system |
accentColor |
string |
"#B85A36" |
Accent color for buttons and focus rings (WCAG AA on white) |
adapters |
FeedbackAdapter[] |
[] |
Client-side adapters. Skipped when apiUrl is in use |
apiUrl |
string |
"/api/feedback" |
Server route the widget POSTs to (recommended for prod) |
collectMetadata |
boolean |
true |
Auto-collect viewport, UA, console errors |
autoScreenshot |
boolean |
false |
Capture screenshot on open via html2canvas |
enableInProduction |
boolean |
false |
Show widget in prod (off by default β safety rail) |
user |
{ name?: string; email?: string } |
β | Reporter identity attached to every submission |
onSuccess |
(payload) => void |
β | Called after successful submission |
onError |
(error) => void |
β | Called when submission fails |
<FeedbackProvider
appName="Checkout"
user={{ name: 'Ananya', email: 'ananya@company.com' }}
>
{children}
</FeedbackProvider>Use the metadata.custom field on every payload β that's the sanctioned extension seam until first-class props land. The receiver sees these in adapter destinations and the audit log.
<FeedbackProvider
appName="Checkout"
user={{ name: user?.name, email: user?.email }}
// The provider doesn't have first-class buildId/gitSha props yet (slated for v0.7).
// Pass via `metadata.custom` on submit using the onReceive hook in your handler,
// or set them as data-* attributes you read in your own onReceive callback:
onSuccess={(payload) => console.log('sent', payload)}
>
{children}
</FeedbackProvider>Server-side, you can read them in your handler's onReceive:
createFeedbackHandler({
adapters: autoAdapters(),
onReceive: async (payload) => {
payload.metadata = {
...payload.metadata!,
custom: {
buildId: process.env.BUILD_ID ?? 'unknown',
gitSha: process.env.GIT_SHA ?? 'unknown',
env: process.env.NODE_ENV ?? 'development',
},
}
return true
},
})First-class top-level props (buildId, gitSha, env) are slated for v0.7.
Declarative routing lets a PM say "checkout bugs go to Slack #checkout + JIRA CHK; growth flag goes to Linear; praise goes to #kudos" without an engineer touching code per change.
// snapfeed.config.ts
import { defineRouting } from 'snapfeed/routing'
export default defineRouting({
routes: [
{ match: '/checkout/**', to: { team: 'payments', slack: '#checkout-feedback', jira: 'CHK' } },
{ flag: 'new_onboarding', to: { team: 'growth', linear: 'GRW' } },
{ category: 'praise', to: { slack: '#kudos' } },
],
default: { team: 'platform', slack: '#bugs' },
})β Shipped in v0.4 β Tier 2 reads the same table from a Google Sheet / CSV so a PM can edit without a deploy. See
snapfeed/routing-sources:csvRoutingSource,googleSheetsRoutingSource,cacheRoutingSource(polling wrapper with last-known-good fallback).
Every smart feature degrades cleanly without an LLM key. The library works fully without one.
| Feature | With LLM | Without LLM |
|---|---|---|
| Title write | Auto-generated from voice/text | First 80 chars of text |
| Severity | Inferred | Reporter picks or default |
| Dedup | Embedding similarity | Exact-match in last 7d |
| Repro steps | Extracted from voice + journey | Raw journey trail shown |
// Real shape (shipped v0.4, current as of v0.6.0)
import { createProvider, applyLLM } from 'snapfeed/llm'
const provider = createProvider({
enabled: true,
provider: 'anthropic', // 'anthropic' | 'openai' | 'ollama' (azure via 'openai' baseURL; bedrock + 'custom' on the v0.7 roadmap)
apiKey: process.env.ANTHROPIC_API_KEY!,
features: { title: true, severity: true, repro: true },
redactBeforeLLM: true,
})
// Then in your handler:
// const enriched = await applyLLM(payload, provider, { budget })
// payload.metadata = { ...payload.metadata, llm: enriched }β Shipped in v0.4 β
snapfeed/llmexposesapplyLLM,createProvider,createBudgetTracker, andredactForLLMwith providers for Anthropic, OpenAI (which also covers Azure OpenAI viaendpoint+headers), and Ollama. Voice capture ships atsnapfeed/voice; screen recording atsnapfeed/screen-recording.
import { slackAdapter } from 'snapfeed/adapters'Per-adapter setup guides: docs/adapters/. Each one walks you through credential setup β env vars β test β common errors in 5 steps. Pair with
npx snapfeed doctorto verify your wiring.
Built-in adapters (alphabetical):
| Adapter | Setup guide | Use it for |
|---|---|---|
asanaAdapter |
docs/adapters/asana.md | Asana task per submission, optional screenshot attachment |
autoAdapters |
docs/adapters/autoAdapters.md | Reads SNAPFEED_* env vars and wires automatically |
clickUpAdapter |
docs/adapters/clickUp.md | ClickUp task with per-category priority |
consoleAdapter |
docs/adapters/console.md | Local dev, debugging |
discordAdapter |
docs/adapters/discord.md | Indie / community / OSS teams |
fileAdapter |
docs/adapters/file.md | Local dev, audit log, Node-only |
githubAdapter |
docs/adapters/github.md | Bug tracking when you live in GitHub |
googleSheetsAdapter |
docs/adapters/googleSheets.md | Lightweight tracking, non-tech editing |
jiraAdapter |
docs/adapters/jira.md | Mid-size / corporate workflows |
linearAdapter |
docs/adapters/linear.md | Startup / product teams |
msTeamsAdapter |
docs/adapters/msTeams.md | Adaptive Card via Teams incoming webhook |
notionAdapter |
docs/adapters/notion.md | Notion page in a database, status + category select properties |
slackAdapter |
docs/adapters/slack.md | Real-time team awareness |
supabaseAdapter |
docs/adapters/supabase.md | Postgres-backed inbox |
telegramAdapter |
docs/adapters/telegram.md | Solo / lightweight notifications |
webhookAdapter |
docs/adapters/webhook.md | Anything else (your own backend) |
snapfeed ships a complete worked example: see examples/custom-adapter/. It builds a Mattermost adapter end-to-end with construction-time validation, payload formatting, error handling, partial-failure surfacing, and tests. Read the example README for the six things to get right when writing your own.
The minimum contract:
import type { FeedbackAdapter } from 'snapfeed/adapters'
export const myAdapter: FeedbackAdapter = {
name: 'my-adapter',
async send(payload) {
await fetch('https://internal.example.com/bugs', {
method: 'POST',
body: JSON.stringify(payload),
})
return { ok: true, deliveryId: 'optional-id' }
},
}See CONTRIBUTING.md for adapter contribution guidelines and the test harness.
| Env var | Adapter |
|---|---|
SNAPFEED_SLACK_WEBHOOK |
Slack |
SNAPFEED_SLACK_USERNAME (optional) |
Slack β bot username override |
SNAPFEED_SLACK_CHANNEL (optional) |
Slack β channel override |
SNAPFEED_DISCORD_WEBHOOK |
Discord |
SNAPFEED_DISCORD_MENTION_ROLE (optional) |
Discord β role to @mention on each post |
SNAPFEED_GITHUB_TOKEN + SNAPFEED_GITHUB_REPO |
GitHub Issues (owner/repo) |
SNAPFEED_TELEGRAM_BOT_TOKEN + SNAPFEED_TELEGRAM_CHAT_ID |
Telegram |
SNAPFEED_WEBHOOK_URL |
Generic webhook |
SNAPFEED_FILE_PATH |
JSONL file |
If none are set in dev, falls back to [fileAdapter, consoleAdapter]. In production, returns [] and warns once.
Threat model: "don't let our own widget become the leak." Defaults reflect that.
- Zero phone-home β no telemetry, no analytics, no relay
- Self-hostable, MIT, no CLA
- Secrets stay server-side (use
apiUrl+createFeedbackHandler, not client-side adapters with tokens) - LLM optional, BYOK only β never proxied through us
- Console-error sanitization strips tokens / keys / JWTs before transit
- Origin allowlist, payload caps, rate limit on the server handler
- See SECURITY.md, THREAT_MODEL.md, docs/SECURITY_REPORT.md, and docs/SECURE_DEPLOYMENT.md
Four levels β pick the one that matches your time budget:
- Theme via CSS variables (5 min) β override
--snapfeed-color-accentetc. in your stylesheet - Compound components (30 min) β
<FeedbackTrigger>,<FeedbackModal>,<FeedbackTextarea>etc. fromsnapfeed/headless; bring your own design system - Slot swap (15 min per slot) β replace one piece (e.g. textarea) via
<FeedbackComponentsProvider>while keeping the rest of the default UI - Headless render-prop (full control) β
<FeedbackHeadless>{state => <YourUI />}</FeedbackHeadless>
import { extendTheme, themeToCss, lightTheme } from 'snapfeed/theme'
const myTheme = extendTheme(lightTheme, { colors: { accent: '#7c3aed' } })
// drop themeToCss(myTheme) into a <style> blockSee docs/customization.md for the full guide and Tailwind / shadcn / Material-UI integration recipes.
Honest read of where snapfeed sits on mobile. The widget is React DOM-based β it runs anywhere a browser does. Native apps need a separate SDK that we haven't built yet.
| Surface | Status | Notes |
|---|---|---|
| Mobile web (responsive sites in mobile browsers) | β supported | Touch targets β₯ 44Γ44 CSS px, hotkey gracefully degrades to the floating button on touch devices, theme tokens auto-detect dark mode via prefers-color-scheme, prefers-reduced-motion honored. Voice + screen recording work on mobile Safari 14.5+ / Chrome Android with one caveat: screen recording is unsupported on iOS Safari (no getDisplayMedia) β the widget feature-detects and hides the button. |
| Progressive Web Apps (PWAs in standalone mode) | β supported | Same widget code; runs in the same WebView the browser uses. No special config needed. |
| React Native (iOS + Android) | π§ v1.0 roadmap | Today snapfeed assumes a browser DOM (window, document, MediaRecorder, html2canvas). React Native has none of those. A separate @snapfeed/react-native package β with native shake-to-report, native screenshot capture, and a React Native-friendly modal β is planned for v1.0. |
| Native iOS / Android (Swift / Kotlin SDKs) | π§ v1.0+ roadmap | Same blocker as React Native; would require separate native SDKs. Best path today: open a WebView pointed at a snapfeed-hosting page on your domain. |
| Capacitor / Cordova / Tauri (web-shell hybrids) | β works (it's still a browser) | Treat as mobile web above. |
If your team needs native mobile feedback today, the most practical workaround is a server-side bridge: the native app POSTs to a
/api/feedbackendpoint that callscreateFeedbackHandlerserver-side. You lose the widget UX (you'd build your own form natively), but you keep all of snapfeed's adapter routing, audit log, and storage. Seedocs/MANUAL.mdΒ§1.2 for the payload contract.
| Agent integration brief | AGENTS.md |
| Quickstart guides (6 personas) | docs/quickstart/ |
| Full reference manual | docs/MANUAL.md |
| Adoption playbook (30/60/90 day) | docs/PLAYBOOK.md |
| Customization (4 levels) | docs/customization.md |
| Architecture + Mermaid diagrams | docs/ARCHITECTURE.md |
| Product requirements (PRD) | docs/PRD.md |
| Security policy + review checklist | SECURITY.md |
| Threat model | THREAT_MODEL.md |
| Audit-style security report | docs/SECURITY_REPORT.md |
| Operator hardening guide | docs/SECURE_DEPLOYMENT.md |
| Privacy posture | PRIVACY.md |
| Compliance (GDPR / SOC 2 / HIPAA / etc.) | COMPLIANCE.md |
| Browser / Node / framework support | COMPATIBILITY.md |
| Versioning policy | VERSIONING.md |
| Release process | RELEASE.md |
| Getting help | SUPPORT.md |
| DPA template | legal/DPA-template.md |
| Third-party notices | legal/THIRD_PARTY_NOTICES.md |
enableInProduction is false by default β the widget is a no-op in NODE_ENV === 'production' unless you opt in. When you enable it for a beta cohort, gate by role so end customers never see it (this is what Kenji from the journey above relies on):
<FeedbackProvider
enableInProduction={user?.role === 'admin' || user?.isBetaTester}
user={{ name: user.name, email: user.email }}
apiUrl="/api/feedback"
>
{children}
</FeedbackProvider>| Phase | Cut as | Highlights |
|---|---|---|
| v0.3 | shipped | Hygiene, file/auto/jira/linear/sheets/discord adapters, routing config, CLI, runnable Next.js example |
| v0.4 | shipped | MS Teams / Asana / ClickUp / Notion adapters; LLM (BYOK β Anthropic, OpenAI, Azure, Ollama); voice capture; screen recording; storage adapters (file, S3-compatible); spreadsheet-backed routing source (Sheets, CSV); audit log; network capture; Release Campaigns; Docker compose self-host stack; minimal admin viewer |
| v0.5 | shipped | UI customization layer (snapfeed/theme + snapfeed/headless); admin dashboard upgrade (filters, bulk actions, dashboard view, audit view, saved views); full doc pack (PRD, Playbook, Manual, Architecture, Security Report, Hardening guide, 6 persona quickstarts); ESLint + Prettier + size-limit; Vite + Remix examples |
| v0.6 (this release) | now | Main-barrel split for browser tree-shaking (snapfeed/adapters + snapfeed/server/security); time-based storage retention (StorageAdapter.delete / listOlderThan, pruneOlderThan helper) for fileStorage + s3Storage; LLM features.redact (second-pass redaction); real React widget tests via jsdom + @testing-library/react; Docker image-digest pinning runbook (docker/pin-digests.sh); README screenshots / visual walkthroughs |
| v0.7 | Postgres-backed inbox + admin write-back; first-class buildId / gitSha / env provider props; built-in OIDC + SAML for admin; SBOM CI workflow; signed offline tarball; GDPR deleteByUserId helper (built on v0.6 retention primitives); Bedrock + custom LLM providers |
|
| v1.0 | React Native SDK + shake-to-report; Vue / Svelte clients (extract @snapfeed/core headless package first); plugin marketplace pattern; ServiceNow / Azure DevOps / Trello adapters |
- Next.js:
examples/nextjs/β App Router,createFeedbackHandler+autoAdapters(). - Vite + React:
examples/vite-react/β SPA with a tiny Express backend usingfeedbackMiddleware. - Remix:
examples/remix/β root provider (client-only) + resource-route action. - Admin dashboard:
examples/admin/β Next.js triage tool with filters, bulk actions, dashboard metrics, audit log view, saved views, CSV export. Reads from the JSONL files written byfileAdapterandfileAuditLog.
See CONTRIBUTING.md. We welcome adapters, accessibility fixes, framework ports, translations, and orchestration examples that show how snapfeed connects to Hermes, OpenClaw, Codex, Claude Code, OpenCode, or your own agent runner.
See CODE_OF_CONDUCT.md.
MIT. See LICENSE.



