翻訳待ち:Show HN: Feedback widget with screenshots, annotations, and AI triage
AI サービスが一時的に利用できないため、復旧後に翻訳を補完します。ソース概要:Uh oh! There was an error while loading. Please reload this page. Notifications You must be signed in to change notification settings Fork 7 Star 51 BranchesTags Open more actions menu Folders and files NameName Last co…
AI サービスが一時的に利用できないため、復旧後に翻訳を補完します。
Uh oh! There was an error while loading. Please reload this page. Notifications You must be signed in to change notification settings Fork 7 Star 51 BranchesTags Open more actions menu Folders and files NameName Last commit message Last commit date Latest commit History 1 Commit 1 Commit assets assets demo demo e2e e2e scripts scripts src src test test .gitignore .gitignore LICENSE LICENSE README.md README.md SELF_HOSTING.md SELF_HOSTING.md package-lock.json package-lock.json package.json package.json test-install-methods.sh test-install-methods.sh tsconfig.json tsconfig.json vite.config.ts vite.config.ts vite.iife.config.ts vite.iife.config.ts vite.standalone.config.ts vite.standalone.config.ts vitest.config.ts vitest.config.ts Repository files navigation Feedback widget with screenshots, annotations, and AI triage. makethisbetter.dev Why You ship with AI agents. Your users hit bugs you never see in dev. They leave. You never find out why. This widget gives your users a way to report exactly what went wrong — annotated screenshot, console errors, DOM state, browser info — in two clicks. AI triage turns that into a structured task your coding agent (Claude Code, Cursor, Codex) picks up automatically. The agent ships the fix. The user gets notified. No more "can you describe what happened?" No more lost screenshots in Slack. The full loop, from frustrated user to shipped fix, runs without you context-switching. Quick Start CDN (2 lines) npm npm install makethisbetter import { MakeThisBetter } from 'makethisbetter' MakeThisBetter.init({ projectKey: 'mtb_proj_YOUR_KEY' }) That's it. A feedback tab appears on your page. How It Works User clicks feedback tab -> Annotates the problem (click to pin, drag to draw) -> Adds a comment -> Submits +-- Screenshot captured automatically +-- Console errors collected +-- Page context assembled (URL, browser, OS, selectors) +-- Sent to Make This Better API +-- AI asks a clarifying question if needed -> Dashboard shows structured feedback -> AI triage produces an agent-ready task -> Your coding agent picks it up and ships the fix -> User gets notified: the fix is live Framework Guides React / Next.js // app/providers.tsx (App Router) or pages/_app.tsx (Pages Router) 'use client' import { useEffect } from 'react' export function FeedbackProvider({ user }: { user?: { id: string, email?: string } }) { useEffect(() => { import('makethisbetter').then(({ MakeThisBetter }) => { MakeThisBetter.init({ projectKey: process.env.NEXT_PUBLIC_MTB_KEY!, user }) }) return () => { import('makethisbetter').then(({ MakeThisBetter }) => MakeThisBetter.destroy()) } }, [user]) return null } Vue / Nuxt // plugins/makethisbetter.client.ts (Nuxt) or main.ts (Vue) import { MakeThisBetter } from 'makethisbetter' export default defineNuxtPlugin(() => { MakeThisBetter.init({ projectKey: useRuntimeConfig().public.mtbKey, }) return { provide: { mtbDestroy: () => MakeThisBetter.destroy() } } }) Astro Rails on every visit, taking any script-appended element with it. Render the host yourself and mark it permanent, or the widget is rebuilt after each navigation and anything mid-flight — a half-written report, a screen recording — is lost. %> The host must sit inside — Turbo pairs permanent elements by id within the body snapshot, so one placed in is never matched and nothing happens. It needs both the id and the attribute; either alone does nothing. Only Turbo-driven apps need this. React, Vue and Svelte routers re-render inside their own container and never replace , so the host survives on its own. Plain HTML / Static Sites Features Annotation Click any element to pin it, or drag to draw a freeform highlight. The SDK captures the element's CSS selector, text content, and position. Interaction Replay Switch to Replay mode in the toolbar to capture an Interaction Replay (up to 60 seconds). It records rrweb DOM mutations and interaction events. It does not capture screen video or audio and does not request browser media permissions. The recorder loads lazily, so there is zero cost until the reporter starts a replay. What a replay contains Captured Not captured The page's DOM structure and every mutation to it Passwords, payment-card data, OTPs, access tokens, private keys, and other high-confidence credentials — replaced with [Filtered] Visible text content and ordinary form values Content inside an element you mark rr-block or rr-mask Mouse positions, clicks, scrolls, viewport size Screen video, audio, camera, microphone Stylesheets needed to render the replay Cookies, localStorage, HTTP request or response bodies Sensitive-data filtering is fixed and cannot be disabled through SDK configuration. Ordinary values such as search queries, issue descriptions, and internal form fields remain available because they are often necessary to reproduce a problem. Excluding an element. Add rrweb's privacy classes to any page region that the SDK must not capture. These classes apply consistently to Interaction Replay, click and input breadcrumbs, annotation metadata, and screenshots: class="rr-block" — hides the entire marked region while preserving its footprint. class="rr-mask" — hides text and form-control content while preserving the surrounding layout. Account balance: $12,400 If screenshot or Replay filtering cannot complete, that attachment is silently omitted and the text feedback still submits. Automated filtering cannot identify every site-specific secret, so use rr-block or rr-mask on sensitive application regions. Frustration Detection The SDK watches for signals that a user is struggling and proactively offers to collect feedback: Signal Trigger Rage click 4+ clicks on the same interaction target within 1.5 seconds Interaction error Uncaught error within 2 seconds after an interaction Dead click (DOM) Command-style control with no response after 1 second Rapid navigation 3+ browser back/forward navigations within 5 seconds Form failure The same form fails validation twice within 30 seconds Error page Landing on a 404/500 error page Disable with frustrationDetection: false. AI Clarification Before submission, an AI assistant may ask one short follow-up question to clarify the real need — avoiding XY problems where users describe their attempted solution instead of the actual problem. Once the exchange is complete, the widget submits the feedback automatically. Auto-Collected Context Every submission automatically includes: Page URL origin and pathname, browser, OS, screen resolution Error type, script pathname, and line/column location (via window.onerror and window.onunhandledrejection) Target element selector and text Annotated screenshot with privacy covers applied before upload (via html-to-image) Annotation coordinates and draw paths Internationalization Built-in support for 7 languages: Code Language en English (default) zh-CN Chinese (Simplified) ja Japanese ko Korean es Spanish fr French de German Configuration MakeThisBetter.init({ // Required projectKey: 'mtb_proj_xxx', // Optional locale: 'en', // UI language. Unset: falls back to , then 'en' position: 'right', // Tab position: 'left' | 'right' tabText: 'Feedback', // Label on the docked tab. Unset: the locale's own wording brandColors: { // Optional semantic colors for the complete Widget primary: '#2563eb', hover: '#1d4ed8', active: '#1e40af', onPrimary: '#ffffff', }, tabColor: '#2563eb', // Legacy launcher-only color; ignored when brandColors is valid entryMode: 'button', // 'button' docks a tab | 'api' renders none theme: 'auto', // 'light' | 'dark' | 'auto' frustrationDetection: true, // Proactive frustration prompts apiUrl: 'https://...', // Self-hosted API endpoint // User identification (recommended). // Ignored entirely when a valid userToken/userTokenFn JWT is present — // see Identity Verification below. user: { id: 'usr_123', email: '[email protected]', name: 'Alex Chen', }, }) Widget branding Use brandColors to apply your product's semantic colors across the launcher, annotation tools, focus and selection states, calls to action, Reporter bubbles, and AI decoration. Supply all four values as six-digit hex colors. The SDK uses them exactly as provided and does not generate a color scale. MakeThisBetter.init({ projectKey: 'mtb_proj_xxx', tabText: 'Report a problem', brandColors: { primary: '#2563eb', hover: '#1d4ed8', active: '#1e40af', onPrimary: '#ffffff', }, }) Success, error, warning, and recording colors keep their state meanings. An incomplete group or any value that is not #RRGGBB rejects the complete group and leaves the default Widget colors in place. tabColor remains available for existing installations and for products that only expose one brand color. It accepts one six-digit hex color, affects only the docked launcher, and derives its launcher hover, active, and foreground colors. When both options are present and brandColors is valid, tabColor is ignored. Omit both options to keep the Make This Better green. The /makethisbetter setup skill recommends brandColors only when it finds a complete semantic group in your design system. When it finds only a primary color, it offers tabColor instead; it never guesses the missing shades. locale is resolved once, in this order: the locale you pass, then the page's attribute, then en. A tag with no exact match is retried without its region (fr-CA → fr), and anything still unmatched falls back to en. Changing the language at runtime MakeThisBetter.setLocale('zh-CN') switches the language for the tab and for anything opened afterwards. A popup that is already on screen keeps the language it was opened in, so a reporter is never re-rendered mid-sentence. Call it before the reporter opens the widget — for example, in the same place your app applies a language change. MakeThisBetter.setLocale('zh-CN') Identity Verification Identity verification links feedback to authenticated users and lets them view their own submissions on the feedback board. Level 0 -- Anonymous (default): No user token. Feedback is anonymous. MakeThisBetter.init({ projectKey: 'mtb_proj_xxx' }) Anonymous reporters are offered a follow-up: the success card shows an optional email field, and an address entered there is sent to the reporter endpoint and kept in localStorage under mtb_reporter_email so the field is not asked for again on later reports from the same browser. The field is skipped entirely when user is set or when a JWT already identifies the reporter. Clearing site data clears it. Level 1 -- Static token: Pass a pre-generated JWT. Simple, but the token may expire during long sessions. MakeThisBetter.init({ projectKey: 'mtb_proj_xxx', userToken: 'eyJhbGciOiJIUzI1NiIs...', }) Level 2 -- Dynamic token (recommended): Pass an async function that returns a fresh JWT. The SDK calls it before each API request, so tokens never go stale. MakeThisBetter.init({ projectKey: 'mtb_proj_xxx', userTokenFn: async () => { const res = await fetch('/api/mtb-token') const { token } = await res.json() return token }, }) When userToken or userTokenFn is set, the widget sends an X-User-Token header with every request. After a successful submission, a "View my feedback" link appears that opens the project board filtered to the user's submissions. The JWT wins over user. These are not two independent ways to name the reporter. Whenever the server receives a valid token, it takes the reporter's id, email and name from the token's sub, email and name claims and discards the user fields the widget sent alongside them — a claim you leave out is simply not recorded, even if user carried it. Put everything you want attributed in the token, and treat user as the anonymous-only path. Generate tokens server-side using you [truncated for AI cost control]