Feedback and support
feedbackLet users report a problem or open a ticket, with the trace, version and steps attached and screenshots redacted.
debug-loopFree in your app, Apache-2.0Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
Let your users report a problem or open a ticket. Each report arrives with the trace, version, device and steps, a redacted screenshot, and its matching error. Use it for a report button.
At a glance
feedback:
tracker: linear
prompt: { onCrash: true, onFailedAction: true }import { createFeedback, StepRecorder } from "mainsdk/feedback";
const { feedback } = createFeedback({ version, device, steps: new StepRecorder(), trace, ask, upload, send });
await feedback.open({ category: "checkout" });Drivers
4 drivers. Select one to see its line in main.yaml.
feedback:
driver: builtinChange this one line to swap providers; your calls stay the same.Built-in and local drivers integration
Codes
No check or runtime codes are assigned to this interface yet.
Features
Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.
| Feature | Phase | Milestone | Spec ticket |
|---|---|---|---|
| Report capture with trace, version and steps | Phase 2 | 0.6 | debug-loop#feedback-capture |
| Support tickets | Phase 2 | 0.6 | Not specified yet |
MainSDK Cloud
Everything above is free in your app. Cloud adds these on top, by plan:
| Plan | What it adds |
|---|---|
| Teams | Inbox history and triage of reports |
Quick start
feedback:
tracker: linear
prompt: { onCrash: true, onFailedAction: true }import { createFeedback, StepRecorder } from "mainsdk/feedback";
// Stand-ins for what your host app provides.
declare const currentTrace: () => string | undefined;
declare const showReportForm: (o: { reason: string }) => Promise<{ text: string } | undefined>;
declare const getqr: { upload: (bytes: Uint8Array, o: { name: string; type: string }) => Promise<{ url: string }> };
declare const cloud: { post: (path: string, body: unknown) => Promise<void> };
export const steps = new StepRecorder();
export const { feedback, support } = createFeedback({
version: "3.1.0",
device: () => ({ platform: "ios", os: "19.1" }),
steps,
trace: () => currentTrace(),
ask: (o) => showReportForm(o), // returns { text, screenshot? } or undefined
upload: (bytes, o) => getqr.upload(bytes, o), // a getqr.id upload; returns { url }
send: (report) => cloud.post("/v1/feedback", report),
prompt: { onCrash: true, onFailedAction: true },
});Configuration
feedback:
tracker: linear # linear | jira | zendesk | plain
prompt: { onCrash: true, onFailedAction: true }What you get
| Call | What it does |
|---|---|
feedback.open({ category? }) | Shows the report form and sends the report. |
feedback.onCrash({ code? }) | Asks after a crash, only when prompt.onCrash is on. |
feedback.onFailedAction({ code }) | Asks after a failed action, only when prompt.onFailedAction is on. |
support.ticket({ subject, body }) | Sends a ticket with the same trace, version and steps. |
Each call returns the report id, or undefined when the customer closed the form or anything failed. A failure never reaches your app (ADR-0003).
Examples
What a report carries
A report holds the trace, the app version, the device, the last steps and, when there is one, a screenshot. Text is redacted for emails, bearer tokens and key formats. Note each step with steps.note("opened checkout"); never put a value from user data in a step.
A screenshot with its text fields blacked out
Give the screenshot as RGBA pixels with the boxes of its text fields. They are filled black before the image is uploaded, and only the redacted image ever leaves the device. The Cloud accepts only a getqr.id link as the screenshot.
import { maskScreenshot } from "mainsdk/feedback";
const width = 2;
const height = 2;
const rgba = new Uint8Array(width * height * 4);
const safe = maskScreenshot({ width, height, rgba, fields: [{ x: 40, y: 120, w: 280, h: 44 }] });Read the inbox
The console inbox groups each report with the error it matches, by trace first and then by error code, so one error and the customers who hit it sit together. Free keeps the free history window; Teams keeps it all and can forward a report to your tracker.
Reply to the reporter
Replies go back to the customer through notify.
import { notify } from "mainsdk";
export async function replyToReport(user: { id: string }, reportId: string) {
await notify(user, { template: "feedback.reply", data: { reportId }, category: "support" });
}Errors and checks
| Code | When | Fix |
|---|---|---|
MAIN-DEBUG-0007 | The Cloud refused a report: a field is missing, or the screenshot is not a getqr.id upload | Send id, text, version, device and steps, and upload the screenshot through getqr.id |
MAIN-DEBUG-0001 | Forwarding a report to a tracker needs Teams | Move the project to Teams |
Swapping drivers
feedback:
tracker: zendesk # was linearYour feedback.open() and support.ticket() calls stay the same. Past tickets stay in the old tool.
Testing
createFeedback takes every port as an argument, so a test passes a fake ask, upload and send and reads what was sent. See Debug your app and Test your app.
Security and privacy
- Screenshots are redacted before they leave the device.
- Support access to a user's data is audited.
- Reports carry the trace, so you see what failed without asking the user for details.
What Cloud adds
Capturing reports is free. Teams adds inbox history and triage of reports. See Free in-app and Cloud.
FAQ
Can reports land in my issue tracker? Teams can forward a report to Linear, Jira, Zendesk or Plain through a forwarder your host supplies.
Why a screenshot link and not the image? Screenshots are uploaded through getqr.id and the Cloud stores only the link (ADR-0009). See Debugging.
Next
Was this page helpful?