@clearlaunch/feedback
v0.1.2
Published
ClearLaunch feedback widget — let your users raise bugs, feature requests and feedback from inside your product. Submissions land on your board as typed cards.
Readme
@clearlaunch/feedback
Let your users raise bugs, feature requests, enhancements and feedback from inside your product. Submissions land on your ClearLaunch board as typed cards.
No dependencies, no framework, ~6kB gzipped.
Install
npm i @clearlaunch/feedbackimport { mountFeedback } from '@clearlaunch/feedback';
mountFeedback({ token: 'fb_…' }); // ClearLaunch → project → FeedbackThat's it — a floating button appears bottom-right, opens a dialog, and submissions arrive on your board.
Script tag, if you'd rather:
<script src="https://unpkg.com/@clearlaunch/feedback/dist/feedback.umd.js"></script>
<script>ClearLaunchFeedback.mountFeedback({ token: 'fb_…' });</script>Triggers
Floating button (default) — a pill in a corner:
mountFeedback({
token: 'fb_…',
trigger: { kind: 'floating', corner: 'bottom-right', inset: 20 },
});Sticky edge tab — clings to an edge, rotated to run along it. offset
positions it along that edge and takes a number (px), a percentage, or any
CSS length:
trigger: { kind: 'sticky', edge: 'right', offset: '50%' } // centred
trigger: { kind: 'sticky', edge: 'left', offset: 0 } // top of the edge
trigger: { kind: 'sticky', edge: 'right', offset: 'calc(100% - 300px)' }Your own control — render nothing and call open():
const fb = mountFeedback({ token: 'fb_…', trigger: { kind: 'none' } });
myButton.addEventListener('click', () => fb.open());
reportBugLink.addEventListener('click', () => fb.open('bug')); // preselect a typeThe dialog
- Type — Bug, Feature, Improvement, Feedback. Restrict or reorder with
types: ['bug', 'feedback']. - Summary and Details.
- Images — paste a screenshot straight into Details, or drop files on the dialog. Up to 5, 5MB each; stored in your project's bucket and attached to the card.
- Email — asked only when you haven't supplied
user.email.
Theming
mountFeedback({
token: 'fb_…',
theme: {
primaryColor: '#8b5cf6',
fontFamily: 'Inter, sans-serif',
borderRadius: '10px',
colorScheme: 'auto', // 'auto' follows the OS; force 'light' | 'dark'
zIndex: 9999, // raise if your own overlays sit above it
},
});Everything is namespaced under .clf- and every property is set explicitly, so
the widget looks the same whatever CSS your page has.
Context and attribution
mountFeedback({
token: 'fb_…',
user: { email: '[email protected]', name: 'Jane' }, // skips the email field
metadata: { plan: 'pro', tenant: 'acme', build: __BUILD_SHA__ },
source: 'support-portal',
});metadata is appended to the card description. source is the important one —
see below.
Reading your tickets back
Every submission carries a source (default 'feedback'), copied onto the
card. Query by it to show your users' tickets inside your product:
GET /api/v1/cards?projectId=…&source=support-portal
GET /api/v1/cards?projectId=…&source=support-portal&cardType=bug
GET /api/v1/cards?projectId=…&source=web,mobile&cardType=bug,enhancementBoth source and cardType accept comma-separated lists. Types map to real
card types, so a "bugs only" filter works:
| Dialog | Card type |
|---|---|
| Bug | bug |
| Feature | feature |
| Improvement | enhancement |
| Feedback | task |
Use distinct source values when you embed on several surfaces ('web-app',
'mobile', 'docs') so you can filter or route each one.
source vs service — they are not the same thing
Both end up on the card and both are filterable, which makes them easy to conflate. They answer different questions:
| | Question it answers | Examples |
|---|---|---|
| source | How did this reach us? The channel. | widget, email, slack, docs |
| service | What part of the product is it about? The touchpoint. | dealership, admin, customer |
One product can have several touchpoints all reporting through the same
channel, which is why one field cannot do both jobs. A dealer raising a bug
through the embedded widget on the dealer portal is source: 'widget',
service: 'dealership'.
// Fixed touchpoint — the usual case. The dealer portal is always 'dealership'.
mountFeedback({ token: 'fb_…', service: 'dealership' });
// Or let the submitter choose, when one page spans several areas:
mountFeedback({ token: 'fb_…', services: ['dealership', 'admin'] });Passing both is refused at mount: fixing a touchpoint and asking the submitter to pick one are contradictory intents.
Touchpoints must first be declared in ClearLaunch → project → Feedback →
Touchpoints. Declaring them is what makes them appear as filters in the
feedback inbox, and @clearlaunch/sdk passes the declared list to the picker
for you. Filter cards by touchpoint the same way as by source:
GET /api/v1/cards?projectId=…&service=dealership
GET /api/v1/cards?projectId=…&service=dealership,admin&cardType=bugIf you have only one surface, ignore service entirely — omitting it is
normal and nothing downstream requires it.
Composing a report
The description field is a markdown composer with a small toolbar — bold, italic, inline code, bulleted list and link. Card descriptions are already markdown, so what the submitter types serialises with no conversion step.
It is a textarea with a toolbar rather than a WYSIWYG editor on purpose: a ProseMirror-class dependency would multiply the size of a widget that gets dropped into someone else's page. The toolbar costs about 0.7 kB gzipped and adds no dependency.
Turn it off where markdown would be noise rather than help — a consumer-facing touchpoint, say:
mountFeedback({ token: 'fb_…', formatting: false });Screenshots
Paste an image straight into the description, or drag one onto the dialog — the two ways people actually share a screenshot. Up to 5 images, 5 MB each; non-images and oversized files are rejected with a message rather than silently dropped.
They are uploaded and attached to the resulting card, where they render as screenshot evidence.
Where submissions land
On your board in To Do by default, so they enter the normal flow. Set a different column in ClearLaunch → project → Feedback if you'd rather triage them separately.
Bugs are raised as p1, everything else p2.
API
const fb = mountFeedback(options);
fb.open(); // open the dialog
fb.open('bug'); // open with a type preselected
fb.close();
fb.destroy(); // remove the trigger, dialog and stylesOptions
| Option | Type | Default |
|---|---|---|
| token | string | required |
| apiUrl | string | hosted ClearLaunch API |
| trigger | sticky | floating | none | { kind: 'floating' } |
| types | FeedbackType[] | all four |
| defaultType | FeedbackType | first in types |
| formatting | boolean | true |
| source | string | 'feedback' |
| service | string | — |
| services | string[] | — |
| user | { email?, name? } | — |
| metadata | Record<string,string> | — |
| theme | FeedbackTheme | — |
| text | Partial<FeedbackText> | English defaults |
| onSubmit / onError / onOpen / onClose | callbacks | — |
Every string in the dialog can be replaced via text, so it can be localised
without forking the package.
Notes
- SSR-safe. With no
documentit returns an inert widget, so importing it never breaks a server render. - The token is public — it identifies your feedback form and is meant to ship in the browser. It only permits submissions, never reads.
- Submissions are rate-limited to 10/min per form.
