{"id":50672679,"url":"https://github.com/shimoverse/snapfeed","last_synced_at":"2026-06-08T12:32:57.291Z","repository":{"id":361784757,"uuid":"1182601446","full_name":"shimoverse/snapfeed","owner":"shimoverse","description":"🔧 Drop-in developer feedback widget for any React/Next.js app. Hotkey-activated, screenshot support, pluggable adapters (Supabase, Telegram, Slack, webhook).","archived":false,"fork":false,"pushed_at":"2026-06-01T08:25:11.000Z","size":7193,"stargazers_count":0,"open_issues_count":8,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-01T09:27:09.994Z","etag":null,"topics":["developer-tools","feedback-widget","nextjs","open-source","react","screenshot","supabase","telegram","typescript"],"latest_commit_sha":null,"homepage":"https://github.com/shimoverse-ops/snapfeed","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shimoverse.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":"THREAT_MODEL.md","audit":null,"citation":"CITATION.cff","codeowners":null,"security":"SECURITY.md","support":"SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-15T18:27:43.000Z","updated_at":"2026-06-01T08:25:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/shimoverse/snapfeed","commit_stats":null,"previous_names":["shimoverse/snapfeed"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/shimoverse/snapfeed","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shimoverse%2Fsnapfeed","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shimoverse%2Fsnapfeed/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shimoverse%2Fsnapfeed/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shimoverse%2Fsnapfeed/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shimoverse","download_url":"https://codeload.github.com/shimoverse/snapfeed/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shimoverse%2Fsnapfeed/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34063150,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["developer-tools","feedback-widget","nextjs","open-source","react","screenshot","supabase","telegram","typescript"],"created_at":"2026-06-08T12:32:57.219Z","updated_at":"2026-06-08T12:32:57.282Z","avatar_url":"https://github.com/shimoverse.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# snapfeed\n\n\u003e Agent-ready feedback loops for internal dogfooding. See it → report it → route it → fix it.\n\n[![npm](https://img.shields.io/npm/v/snapfeed.svg)](https://www.npmjs.com/package/snapfeed)\n[![license](https://img.shields.io/npm/l/snapfeed.svg)](./LICENSE)\n[![CI](https://github.com/shimoverse/snapfeed/actions/workflows/ci.yml/badge.svg)](https://github.com/shimoverse/snapfeed/actions)\n[![types](https://img.shields.io/badge/types-built--in-blue)](#)\n\nsnapfeed 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.\n\nUse 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.\n\nNot 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.\n\n![snapfeed animated demo — bug spotted, feedback sent, coding agent fixes, tests pass, and the change is deployed](./docs/screenshots/snapfeed-demo.gif)\n\n\u003e 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.\n\n## Pick your mode\n\n| Mode | For | Setup |\n|------|-----|-------|\n| 🚀 Cloud-relayed | Indie / hackathon / small startup | 5 min |\n| 🏢 Self-hosted | Startup → mid-size | 30 min |\n| 🔒 Air-gapped | Corp / regulated | 1-2 weeks (incl. security review) |\n\nSame widget. Different backend topology. Pick based on what your IT will approve.\n\n**New here? Choose one path:**\n\n1. **Try it locally:** run the 60-second quickstart below; feedback lands in `feedback.jsonl`.\n2. **Wire your team:** add one server-side destination such as Slack, GitHub, Linear, JIRA, or webhook.\n3. **Ship it safely:** follow the [production checklist](./docs/PRODUCTION_CHECKLIST.md) before enabling the widget beyond local/staging.\n\n**Per-persona quickstart guides** (5 min → 1 hour, copy-paste runnable): see [docs/quickstart/](./docs/quickstart/index.md) for indie, startup, mid-size, corp, OSS-maintainer, and designer walkthroughs.\n\n## Agent-ready feedback loops\n\nMost feedback tools assume a human PM reads the ticket later. snapfeed assumes you may already have a full agent architecture:\n\n| Reviewer | What they inspect | Where snapfeed sends it |\n|---|---|---|\n| QA agent | broken flows, failed checks, regressions | GitHub Issue, Linear/JIRA bug, webhook to orchestrator |\n| Design agent | layout, copy, accessibility, visual diffs | Slack/design channel, design QA queue, webhook |\n| PM agent | acceptance criteria, launch blockers, prioritization | roadmap board, issue tracker, triage inbox |\n| Human tester/designer | anything that feels wrong in the actual product | the same route, same payload, same audit trail |\n\nThe usual loop looks like this:\n\n```text\nreviewer agent or human\n  → snapfeed widget / feedback API\n  → /api/feedback with screenshot + URL + metadata + target element context\n  → Slack/JIRA/Linear/GitHub/webhook\n  → orchestrator assigns coding agent\n  → fix PR → tests → deployment\n```\n\nsnapfeed 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.\n\n### Element context for coding agents\n\nWhen `collectElementContext` is enabled (default), snapfeed remembers the last host-app element the reviewer clicked or focused before opening the widget. The submitted `FeedbackPayload` includes a bounded `target` object with a selector, DOM path, tag, ARIA label, visible text, component hint from `data-component` / `data-snapfeed-component`, viewport bounds, and a small computed-style snapshot. snapfeed-owned UI is marked with `data-snapfeed-ui` and ignored, so pressing Send does not replace the original app target.\n\nThis is the Agentation-style precision layer inside snapfeed's broader workflow: agents get enough detail to grep or inspect the exact UI element, while snapfeed still routes the feedback to Slack, GitHub, Linear, JIRA, Supabase, or your orchestrator. GitHub and Slack destinations show the target selector; Supabase stores it under `metadata.target`; webhooks receive the full payload.\n\n```json\n{\n  \"text\": \"Button copy feels risky\",\n  \"pageUrl\": \"https://staging.example.com/checkout\",\n  \"target\": {\n    \"tagName\": \"button\",\n    \"selector\": \"[data-testid=\\\"pay-now\\\"]\",\n    \"domPath\": \"body \u003e main \u003e button.primary\",\n    \"componentName\": \"CheckoutButton\",\n    \"ariaLabel\": \"Pay now\",\n    \"text\": \"Pay now\"\n  }\n}\n```\n\nDisable it for privacy-sensitive surfaces with `\u003cFeedbackProvider collectElementContext={false}\u003e` or override `payload.target` when submitting programmatically.\n\n### Agent install and verification checklist\n\nIf you are an AI coding agent adding snapfeed to a repo, do this in order:\n\n1. Read [`AGENTS.md`](./AGENTS.md) for the deterministic install path.\n2. Run `npm install snapfeed` and `npx snapfeed init --yes`.\n3. Mount `\u003cFeedbackProvider\u003e` in the app root and pass authenticated `user` data if available.\n4. Configure one destination. For an agent loop, use `SNAPFEED_WEBHOOK_URL` or a custom adapter that posts to your orchestrator.\n5. Run `npx snapfeed doctor`, then `npx snapfeed doctor --prod` before enabling production.\n6. Smoke test the handler:\n\n```bash\ncurl -X POST http://localhost:3000/api/feedback \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"text\":\"agent smoke test\",\"appName\":\"MyApp\",\"pageUrl\":\"http://localhost:3000\",\"pageName\":\"Home\",\"timestamp\":\"2026-04-26T12:00:00Z\"}'\n```\n\nSuccess 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.\n\n## 60-second quickstart (zero config)\n\n```bash\nnpm install snapfeed\nnpx snapfeed init --yes\n```\n\nThen wrap your root layout (this is the one step the CLI can't do for you):\n\n```tsx\n// app/layout.tsx (Next.js App Router) — or your equivalent root component\nimport { FeedbackProvider } from 'snapfeed'\n\nexport default function RootLayout({ children }) {\n  return (\n    \u003chtml\u003e\n      \u003cbody\u003e\n        \u003cFeedbackProvider appName=\"My App\"\u003e{children}\u003c/FeedbackProvider\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n  )\n}\n```\n\nFor 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.\n\n```bash\nnpm run dev\n```\n\nPress **Ctrl+Shift+F** (Cmd+Shift+F on Mac). Feedback dumps to `./feedback.jsonl` and your browser console. No env vars, no adapters, no signup.\n\n**Wire a real destination — one env var, then restart `npm run dev`:**\n\n```bash\necho 'SNAPFEED_SLACK_WEBHOOK=\u003cyour Slack webhook URL\u003e' \u003e\u003e .env.local\n```\n\n\u003e Need a Slack webhook URL? https://api.slack.com/messaging/webhooks (5 steps).\n\nThe 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.\n\n\u003e **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=\u003curl\u003e` to check your dev server's `/api/feedback` route is reachable. Before shipping beyond local/staging, run `npx snapfeed doctor --prod` to also check for explicit `allowedOrigins` and `rateLimit` guardrails. **Always your first-stop debug command.**\n\n## What it does (the review journey)\n\n| 1. Open | 2. Describe | 3. Routed |\n|---|---|---|\n| ![Open: empty form with category chips and auto-captured screenshot](./docs/screenshots/widget-open-empty.png) | ![Describe: bug category picked + one-sentence repro](./docs/screenshots/widget-open-filled.png) | ![Sent: confirmation surface](./docs/screenshots/widget-success.png) |\n| Hotkey, click, or API trigger. The form mounts with screenshot, identity, page context, and category chips. | A human or reviewer agent adds one sentence. Screenshot annotation, voice, and console-error attachment can happen here. | Routes server-side to Slack/JIRA/Linear/GitHub/webhook. The reporter sees confirmation; your PM/QA/design/coding agents get the work item. |\n\n**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.\n\n**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.\n\n**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.\n\n**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.\n\n## The three modes in detail\n\n### 🚀 Cloud-relayed\nFor 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.\n\n```ts\n// app/api/feedback/route.ts\nimport { createFeedbackHandler } from 'snapfeed/server/nextjs'\nimport { autoAdapters } from 'snapfeed/adapters'\n\nexport const POST = createFeedbackHandler({\n  adapters: autoAdapters(),\n  // Required before enabling the widget beyond local/staging:\n  allowedOrigins: ['https://staging.example.com'],\n  rateLimit: { max: 10, windowMs: 60_000 },\n})\n```\n\n### 🏢 Self-hosted\nFor 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:\n\n```bash\ncd docker\ncp .env.example .env\ndocker compose up\n```\n\nThen point the widget at `http://\u003chost\u003e:8787/feedback`. Add `--profile llm` to also start a local Ollama. See [docker/README.md](./docker/README.md). Postgres-backed inbox + admin write-back are slated for **v0.7**.\n\nFor deployments where you'd rather host the worker yourself in your existing Node app:\n\n```ts\n// app/api/feedback/route.ts\nimport { createFeedbackHandler } from 'snapfeed/server/nextjs'\nimport { supabaseAdapter, slackAdapter } from 'snapfeed/adapters'\n\nexport const POST = createFeedbackHandler({\n  adapters: [\n    supabaseAdapter({\n      url: process.env.SUPABASE_URL!,\n      serviceKey: process.env.SUPABASE_SERVICE_ROLE_KEY!,\n    }),\n    slackAdapter({ webhookUrl: process.env.SLACK_WEBHOOK! }),\n  ],\n  rateLimit: { max: 10, windowMs: 60_000 },\n  allowedOrigins: ['https://staging.myapp.com', /\\.myapp\\.com$/],\n})\n```\n\n### 🔒 Air-gapped\nFor 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](./docker/README.md) for the install guide and [SECURITY.md](./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](./SECURITY.md) §Coming in later releases).\n\n## Persona picker\n\n| Persona | Most likely destinations | Most likely mode |\n|---------|--------------------------|------------------|\n| Indie / OSS maintainer | GitHub Issues, Discord, file | Cloud-relayed |\n| Startup founder/PM | Slack, Linear, Sheet | Cloud-relayed → Self-hosted |\n| Mid-size eng manager | Slack, JIRA, Postgres | Self-hosted |\n| Corp eng / QA lead | JIRA, ServiceNow, MS Teams | Air-gapped |\n| Designer (any team) | Whatever their team set up | n/a — they just press the hotkey |\n\n## Configuration\n\n### Provider props\n\n```tsx\nimport { FeedbackProvider } from 'snapfeed'\n\n\u003cFeedbackProvider appName=\"Checkout\" hotkey=\"ctrl+shift+f\"\u003e\n  {children}\n\u003c/FeedbackProvider\u003e\n```\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `appName` | `string` | `\"App\"` | Shown in UI and in adapter notifications |\n| `hotkey` | `string` | `\"ctrl+shift+f\"` | Format: `\"ctrl+shift+f\"`, `\"meta+k\"`, `\"ctrl+alt+b\"` |\n| `position` | `\"bottom-right\" \\| \"bottom-left\" \\| \"top-right\" \\| \"top-left\"` | `\"bottom-right\"` | Floating trigger position |\n| `theme` | `\"auto\" \\| \"light\" \\| \"dark\"` | `\"auto\"` | Color theme; `auto` follows system |\n| `accentColor` | `string` | `\"#B85A36\"` | Accent color for buttons and focus rings (WCAG AA on white) |\n| `adapters` | `FeedbackAdapter[]` | `[]` | Client-side adapters. Skipped when `apiUrl` is in use |\n| `apiUrl` | `string` | `\"/api/feedback\"` | Server route the widget POSTs to (recommended for prod) |\n| `collectMetadata` | `boolean` | `true` | Auto-collect viewport, UA, console errors |\n| `collectElementContext` | `boolean` | `true` | Auto-attach last clicked/focused host element selector, DOM path, ARIA/text, component hint, bounds, and style snapshot for coding agents |\n| `autoScreenshot` | `boolean` | `false` | Capture screenshot on open via `html2canvas` |\n| `enableInProduction` | `boolean` | `false` | Show widget in prod (off by default — safety rail) |\n| `user` | `{ name?: string; email?: string }` | — | Reporter identity attached to every submission |\n| `onSuccess` | `(payload) =\u003e void` | — | Called after successful submission |\n| `onError` | `(error) =\u003e void` | — | Called when submission fails |\n\n### Identifying the reporter\n\n```tsx\n\u003cFeedbackProvider\n  appName=\"Checkout\"\n  user={{ name: 'Ananya', email: 'ananya@company.com' }}\n\u003e\n  {children}\n\u003c/FeedbackProvider\u003e\n```\n\n### Attaching build context (gitSha, buildId, env, feature flags)\n\nUse 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.\n\n```tsx\n\u003cFeedbackProvider\n  appName=\"Checkout\"\n  user={{ name: user?.name, email: user?.email }}\n  // The provider doesn't have first-class buildId/gitSha props yet (slated for v0.7).\n  // Pass via `metadata.custom` on submit using the onReceive hook in your handler,\n  // or set them as data-* attributes you read in your own onReceive callback:\n  onSuccess={(payload) =\u003e console.log('sent', payload)}\n\u003e\n  {children}\n\u003c/FeedbackProvider\u003e\n```\n\nServer-side, you can read them in your handler's `onReceive`:\n\n```ts\ncreateFeedbackHandler({\n  adapters: autoAdapters(),\n  onReceive: async (payload) =\u003e {\n    payload.metadata = {\n      ...payload.metadata!,\n      custom: {\n        buildId: process.env.BUILD_ID ?? 'unknown',\n        gitSha: process.env.GIT_SHA ?? 'unknown',\n        env: process.env.NODE_ENV ?? 'development',\n      },\n    }\n    return true\n  },\n})\n```\n\nFirst-class top-level props (`buildId`, `gitSha`, `env`) are slated for **v0.7**.\n\n### Routing config\n\nDeclarative 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.\n\n```ts\n// snapfeed.config.ts\nimport { defineRouting } from 'snapfeed/routing'\n\nexport default defineRouting({\n  routes: [\n    { match: '/checkout/**', to: { team: 'payments', slack: '#checkout-feedback', jira: 'CHK' } },\n    { flag: 'new_onboarding', to: { team: 'growth', linear: 'GRW' } },\n    { category: 'praise', to: { slack: '#kudos' } },\n  ],\n  default: { team: 'platform', slack: '#bugs' },\n})\n```\n\n\u003e ✅ 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).\n\n### LLM (BYOK, optional)\n\nEvery smart feature degrades cleanly without an LLM key. The library works fully without one.\n\n| Feature | With LLM | Without LLM |\n|---------|----------|-------------|\n| Title write | Auto-generated from voice/text | First 80 chars of text |\n| Severity | Inferred | Reporter picks or default |\n| Dedup | Embedding similarity | Exact-match in last 7d |\n| Repro steps | Extracted from voice + journey | Raw journey trail shown |\n\n```ts\n// Real shape (shipped v0.4, current as of v0.6.0)\nimport { createProvider, applyLLM } from 'snapfeed/llm'\n\nconst provider = createProvider({\n  enabled: true,\n  provider: 'anthropic', // 'anthropic' | 'openai' | 'ollama'  (azure via 'openai' baseURL; bedrock + 'custom' on the v0.7 roadmap)\n  apiKey: process.env.ANTHROPIC_API_KEY!,\n  features: { title: true, severity: true, repro: true },\n  redactBeforeLLM: true,\n})\n\n// Then in your handler:\n//   const enriched = await applyLLM(payload, provider, { budget })\n//   payload.metadata = { ...payload.metadata, llm: enriched }\n```\n\n\u003e ✅ Shipped in **v0.4** — `snapfeed/llm` exposes `applyLLM`, `createProvider`, `createBudgetTracker`, and `redactForLLM` with providers for Anthropic, OpenAI (which also covers Azure OpenAI via `endpoint` + `headers`), and Ollama. Voice capture ships at `snapfeed/voice`; screen recording at `snapfeed/screen-recording`.\n\n## Adapters\n\n```ts\nimport { slackAdapter } from 'snapfeed/adapters'\n```\n\n\u003e **Per-adapter setup guides:** [docs/adapters/](./docs/adapters/index.md). Each one walks you through credential setup → env vars → test → common errors in 5 steps. Pair with `npx snapfeed doctor` to verify your wiring.\n\nBuilt-in adapters (alphabetical):\n\n| Adapter | Setup guide | Use it for |\n|---------|-------------|------------|\n| `asanaAdapter` | [docs/adapters/asana.md](./docs/adapters/asana.md) | Asana task per submission, optional screenshot attachment |\n| `autoAdapters` | [docs/adapters/autoAdapters.md](./docs/adapters/autoAdapters.md) | Reads `SNAPFEED_*` env vars and wires automatically |\n| `clickUpAdapter` | [docs/adapters/clickUp.md](./docs/adapters/clickUp.md) | ClickUp task with per-category priority |\n| `consoleAdapter` | [docs/adapters/console.md](./docs/adapters/console.md) | Local dev, debugging |\n| `discordAdapter` | [docs/adapters/discord.md](./docs/adapters/discord.md) | Indie / community / OSS teams |\n| `fileAdapter` | [docs/adapters/file.md](./docs/adapters/file.md) | Local dev, audit log, Node-only |\n| `githubAdapter` | [docs/adapters/github.md](./docs/adapters/github.md) | Bug tracking when you live in GitHub |\n| `googleSheetsAdapter` | [docs/adapters/googleSheets.md](./docs/adapters/googleSheets.md) | Lightweight tracking, non-tech editing |\n| `jiraAdapter` | [docs/adapters/jira.md](./docs/adapters/jira.md) | Mid-size / corporate workflows |\n| `linearAdapter` | [docs/adapters/linear.md](./docs/adapters/linear.md) | Startup / product teams |\n| `msTeamsAdapter` | [docs/adapters/msTeams.md](./docs/adapters/msTeams.md) | Adaptive Card via Teams incoming webhook |\n| `notionAdapter` | [docs/adapters/notion.md](./docs/adapters/notion.md) | Notion page in a database, status + category select properties |\n| `slackAdapter` | [docs/adapters/slack.md](./docs/adapters/slack.md) | Real-time team awareness |\n| `supabaseAdapter` | [docs/adapters/supabase.md](./docs/adapters/supabase.md) | Postgres-backed inbox |\n| `telegramAdapter` | [docs/adapters/telegram.md](./docs/adapters/telegram.md) | Solo / lightweight notifications |\n| `webhookAdapter` | [docs/adapters/webhook.md](./docs/adapters/webhook.md) | Anything else (your own backend) |\n\n### Writing a custom adapter\n\nsnapfeed ships a complete worked example: see [`examples/custom-adapter/`](./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.\n\nThe minimum contract:\n\n```ts\nimport type { FeedbackAdapter } from 'snapfeed/adapters'\n\nexport const myAdapter: FeedbackAdapter = {\n  name: 'my-adapter',\n  async send(payload) {\n    await fetch('https://internal.example.com/bugs', {\n      method: 'POST',\n      body: JSON.stringify(payload),\n    })\n    return { ok: true, deliveryId: 'optional-id' }\n  },\n}\n```\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) for adapter contribution guidelines and the test harness.\n\n## Auto-adapter env vars\n\n| Env var | Adapter |\n|---------|---------|\n| `SNAPFEED_SLACK_WEBHOOK` | Slack |\n| `SNAPFEED_SLACK_USERNAME` (optional) | Slack — bot username override |\n| `SNAPFEED_SLACK_CHANNEL` (optional) | Slack — channel override |\n| `SNAPFEED_DISCORD_WEBHOOK` | Discord |\n| `SNAPFEED_DISCORD_MENTION_ROLE` (optional) | Discord — role to @mention on each post |\n| `SNAPFEED_GITHUB_TOKEN` + `SNAPFEED_GITHUB_REPO` | GitHub Issues (`owner/repo`) |\n| `SNAPFEED_TELEGRAM_BOT_TOKEN` + `SNAPFEED_TELEGRAM_CHAT_ID` | Telegram |\n| `SNAPFEED_WEBHOOK_URL` | Generic webhook |\n| `SNAPFEED_FILE_PATH` | JSONL file |\n\nIf none are set in dev, falls back to `[fileAdapter, consoleAdapter]`. In production, returns `[]` and warns once.\n\n## Security\n\nThreat model: \"don't let our own widget become the leak.\" Defaults reflect that.\n\n- Zero phone-home — no telemetry, no analytics, no relay\n- Self-hostable, MIT, no CLA\n- Secrets stay server-side (use `apiUrl` + `createFeedbackHandler`, not client-side adapters with tokens)\n- LLM optional, BYOK only — never proxied through us\n- Console-error sanitization strips tokens / keys / JWTs before transit\n- Origin allowlist, payload caps, rate limit on the server handler\n- See [SECURITY.md](./SECURITY.md), [THREAT_MODEL.md](./THREAT_MODEL.md), [docs/SECURITY_REPORT.md](./docs/SECURITY_REPORT.md), and [docs/SECURE_DEPLOYMENT.md](./docs/SECURE_DEPLOYMENT.md)\n\n## Customization\n\nFour levels — pick the one that matches your time budget:\n\n1. **Theme via CSS variables** (5 min) — override `--snapfeed-color-accent` etc. in your stylesheet\n2. **Compound components** (30 min) — `\u003cFeedbackTrigger\u003e`, `\u003cFeedbackModal\u003e`, `\u003cFeedbackTextarea\u003e` etc. from `snapfeed/headless`; bring your own design system\n3. **Slot swap** (15 min per slot) — replace one piece (e.g. textarea) via `\u003cFeedbackComponentsProvider\u003e` while keeping the rest of the default UI\n4. **Headless render-prop** (full control) — `\u003cFeedbackHeadless\u003e{state =\u003e \u003cYourUI /\u003e}\u003c/FeedbackHeadless\u003e`\n\n```tsx\nimport { extendTheme, themeToCss, lightTheme } from 'snapfeed/theme'\n\nconst myTheme = extendTheme(lightTheme, { colors: { accent: '#7c3aed' } })\n// drop themeToCss(myTheme) into a \u003cstyle\u003e block\n```\n\nSee [docs/customization.md](./docs/customization.md) for the full guide and Tailwind / shadcn / Material-UI integration recipes.\n\n## Mobile support\n\nHonest 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.\n\n| Surface | Status | Notes |\n|---|---|---|\n| **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. |\n| **Progressive Web Apps** (PWAs in standalone mode) | ✅ supported | Same widget code; runs in the same WebView the browser uses. No special config needed. |\n| **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. |\n| **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. |\n| **Capacitor / Cordova / Tauri** (web-shell hybrids) | ✅ works (it's still a browser) | Treat as mobile web above. |\n\n\u003e If your team needs native mobile feedback today, the most practical workaround is a server-side bridge: the native app POSTs to a `/api/feedback` endpoint that calls `createFeedbackHandler` server-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. See [`docs/MANUAL.md` §1.2](./docs/MANUAL.md#12-adapters) for the payload contract.\n\n## Documentation\n\n| | |\n|---|---|\n| Agent integration brief | [AGENTS.md](./AGENTS.md) |\n| Quickstart guides (6 personas) | [docs/quickstart/](./docs/quickstart/index.md) |\n| Full reference manual | [docs/MANUAL.md](./docs/MANUAL.md) |\n| Adoption playbook (30/60/90 day) | [docs/PLAYBOOK.md](./docs/PLAYBOOK.md) |\n| Customization (4 levels) | [docs/customization.md](./docs/customization.md) |\n| Architecture + Mermaid diagrams | [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) |\n| Product requirements (PRD) | [docs/PRD.md](./docs/PRD.md) |\n| Security policy + review checklist | [SECURITY.md](./SECURITY.md) |\n| Threat model | [THREAT_MODEL.md](./THREAT_MODEL.md) |\n| Audit-style security report | [docs/SECURITY_REPORT.md](./docs/SECURITY_REPORT.md) |\n| Operator hardening guide | [docs/SECURE_DEPLOYMENT.md](./docs/SECURE_DEPLOYMENT.md) |\n| Privacy posture | [PRIVACY.md](./PRIVACY.md) |\n| Compliance (GDPR / SOC 2 / HIPAA / etc.) | [COMPLIANCE.md](./COMPLIANCE.md) |\n| Browser / Node / framework support | [COMPATIBILITY.md](./COMPATIBILITY.md) |\n| Versioning policy | [VERSIONING.md](./VERSIONING.md) |\n| Release process | [RELEASE.md](./RELEASE.md) |\n| Getting help | [SUPPORT.md](./SUPPORT.md) |\n| DPA template | [legal/DPA-template.md](./legal/DPA-template.md) |\n| Third-party notices | [legal/THIRD_PARTY_NOTICES.md](./legal/THIRD_PARTY_NOTICES.md) |\n\n## Production safety\n\n`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):\n\n```tsx\n\u003cFeedbackProvider\n  enableInProduction={user?.role === 'admin' || user?.isBetaTester}\n  user={{ name: user.name, email: user.email }}\n  apiUrl=\"/api/feedback\"\n\u003e\n  {children}\n\u003c/FeedbackProvider\u003e\n```\n\n## Roadmap\n\n| Phase | Cut as | Highlights |\n|-------|--------|------------|\n| v0.3 | shipped | Hygiene, file/auto/jira/linear/sheets/discord adapters, routing config, CLI, runnable Next.js example |\n| 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 |\n| 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 |\n| 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 |\n| 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 |\n| 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 |\n\n## Examples\n\n- **Next.js**: [`examples/nextjs/`](./examples/nextjs/) — App Router, `createFeedbackHandler` + `autoAdapters()`.\n- **Vite + React**: [`examples/vite-react/`](./examples/vite-react/) — SPA with a tiny Express backend using `feedbackMiddleware`.\n- **Remix**: [`examples/remix/`](./examples/remix/) — root provider (client-only) + resource-route action.\n- **Admin dashboard**: [`examples/admin/`](./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 by `fileAdapter` and `fileAuditLog`.\n\n## Contributing\n\nSee [CONTRIBUTING.md](./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.\n\n## Code of conduct\n\nSee [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).\n\n## License\n\nMIT. See [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshimoverse%2Fsnapfeed","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshimoverse%2Fsnapfeed","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshimoverse%2Fsnapfeed/lists"}