reference
every prop, every option.
naming
three things in remediate are called metadata. they're related but not the same:
metadata(prop) —<Remediate metadata={{...}}>. extra data you attach to every submission. ends up atsubmission.metadataon the server, verbatim.metadata(form field) — internal name of the json envelope inside the multipartPOST. you only see this if you build FormData by hand or curl the route;parseFeedbackhides it.submission.metadata(json field) — the value of themetadataprop, sitting on the parsedFeedbackSubmission.
day-to-day you only touch the prop. the form field is a wire-format detail.
<Remediate />
all props optional. omit endpoint and onSubmit and the widget renders but submissions go nowhere.
<RemediateTrigger />
replace the default floating button with your own trigger.
import { Remediate, RemediateTrigger } from "remediate";
<Remediate endpoint="/api/feedback">
<RemediateTrigger asChild>
<button>report a bug</button>
</RemediateTrigger>
</Remediate>asChild (radix-style) merges props onto the child. omit it for the default button.
imperative api
import { remediate } from "remediate";
remediate.open();
remediate.close();
remediate.submit(); // submit the current draftparseFeedback(req)
import { parseFeedback } from "remediate/server";
const { submission, files } = await parseFeedback(req);req is a web Request. returns Promise<ParsedFeedback>. see payload.
throws on malformed multipart bodies. wrap in try/catch.
runtime support
route.ts exporting POST(req: Request)parseFeedbackPages(req, res)loader/actionc.req.rawparseFeedbackExpress(req)RequestRequesttoMarkdown(submission, options?)
import { parseFeedback, toMarkdown } from "remediate/server";
const { submission } = await parseFeedback(req);
const body = toMarkdown(submission);formats a submission as structured markdown. items grouped by type (annotations → text notes → captures), numbered to match the widget, priority badges, environment line.
works directly for github issues, linear, discord embeds, and email.
cors
if your widget origin and endpoint origin differ, the browser will preflight. respond with:
return Response.json(
{ ok: true },
{
headers: {
"Access-Control-Allow-Origin": "https://your-app.com",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
},
);handle the OPTIONS preflight too.
csp
remediate needs:
media-src 'self' blob:
img-src 'self' data: blob:
connect-src 'self' <your-endpoint-origin>
worker-src 'self' blob:worker-src only if you use video capture.
bundle
- core: ~14kb gzipped
- video capture: +8kb (lazy-loaded on first open)
- voice capture: +2kb (lazy-loaded on first open)
"sideEffects": false. tree-shakes.
css
styles inject on mount under [data-remediate-widget]. the prefix is namespaced. no global resets, no tailwind reset interaction. override via:
[data-remediate-widget] { --rmd-accent: #ff5722; }