@mushi-mushi/web 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -36,12 +36,27 @@ interface WidgetRewardsState {
36
36
  /** Expected base points for a `report_submit` action (default 50). */
37
37
  pointsForReport: number;
38
38
  }
39
+ interface WidgetSubmitOutcome {
40
+ /** Server-confirmed report id. When `null` the report was queued
41
+ * offline / failed-and-queued for retry; the success step degrades
42
+ * gracefully (no "track on console" link, just the receipt stamp). */
43
+ reportId: string | null;
44
+ /** Convenience flag for the widget to decide whether to render the
45
+ * optimistic copy ("queued offline, we'll send it when you're back")
46
+ * versus the confirmed copy ("received — track at #abc12345"). */
47
+ queuedOffline?: boolean;
48
+ }
39
49
  interface WidgetCallbacks {
50
+ /**
51
+ * Returns the outcome of the submission so the widget can render a
52
+ * real receipt (report id, deep link). Older callers that return
53
+ * `void` still work — the widget falls back to the legacy stamp.
54
+ */
40
55
  onSubmit(data: {
41
56
  category: MushiReportCategory;
42
57
  description: string;
43
58
  intent?: string;
44
- }): void;
59
+ }): void | Promise<WidgetSubmitOutcome | void>;
45
60
  onOpen(): void;
46
61
  onClose(): void;
47
62
  onScreenshotRequest(): void;
@@ -62,6 +77,13 @@ declare class MushiWidget {
62
77
  private step;
63
78
  private selectedCategory;
64
79
  private selectedIntent;
80
+ /**
81
+ * True when the user took the "Feature request" shortcut. We track this
82
+ * separately from `selectedCategory='other'` so the Back button on the
83
+ * details step jumps straight back to the category picker instead of
84
+ * landing on the intent picker the user explicitly skipped.
85
+ */
86
+ private viaFeatureRequest;
65
87
  private screenshotAttached;
66
88
  private screenshotCapturing;
67
89
  private screenshotError;
@@ -99,14 +121,36 @@ declare class MushiWidget {
99
121
  private successTimer;
100
122
  private autoCloseTimer;
101
123
  private rewardsState;
124
+ /** Server-confirmed id for the just-submitted report. Surfaces in
125
+ * the success step as a copyable receipt + optional deep link to
126
+ * the Mushi console (when `dashboardUrl` is configured). Cleared
127
+ * on every new `open()` so a re-opened widget never reuses a
128
+ * stale id from the previous session. */
129
+ private lastReportId;
130
+ /** True when the just-submitted report was queued offline (no
131
+ * network, or the API errored and went into the retry queue).
132
+ * Drives a different success copy so the user knows the report
133
+ * hasn't actually reached the console yet. */
134
+ private lastSubmitQueuedOffline;
135
+ /** Whether the user has clicked ✕ on the header banner this session. */
136
+ private bannerDismissed;
102
137
  constructor(config: MushiWidgetConfig | undefined, callbacks: WidgetCallbacks, sdkVersion?: string);
103
138
  mount(): void;
104
139
  getIsMounted(): boolean;
105
140
  updateConfig(config?: MushiWidgetConfig): void;
106
141
  open(options?: {
107
142
  category?: MushiReportCategory;
143
+ featureRequest?: boolean;
108
144
  }): void;
109
145
  close(): void;
146
+ /**
147
+ * Briefly highlight the trigger button (a soft pulse + tooltip) without
148
+ * opening the full reporter panel. Use for first-session welcome nudges
149
+ * and other "by the way, this exists" prompts where forcing the panel
150
+ * open would feel aggressive. Honours `position: 'none'` (no-op when
151
+ * the trigger button is hidden).
152
+ */
153
+ pulseTrigger(): void;
110
154
  getIsOpen(): boolean;
111
155
  showTrigger(): void;
112
156
  hideTrigger(): void;
@@ -138,6 +182,13 @@ declare class MushiWidget {
138
182
  private syncAttachedLaunchers;
139
183
  private syncSmartHide;
140
184
  private shouldRenderTrigger;
185
+ /** Height of the banner in px — kept in sync with the CSS `.mushi-banner` height (36px). */
186
+ private static readonly BANNER_HEIGHT;
187
+ /** CSS property applied to document.body so host-app content doesn't slide under the banner. */
188
+ private static readonly BODY_NUDGE_PROP;
189
+ private applyBodyNudge;
190
+ private removeBodyNudge;
191
+ private renderBanner;
141
192
  private effectiveTrigger;
142
193
  private isMobileSmartHidden;
143
194
  private detectEnvironment;
@@ -166,6 +217,19 @@ declare class MushiWidget {
166
217
  */
167
218
  private renderStepIndicator;
168
219
  private renderCategoryStep;
220
+ /**
221
+ * First-class "Feature request" entry rendered at the top of the
222
+ * category step. Beta apps consistently get more useful signal when
223
+ * the user has a no-friction path to say "I wish this did X" — burying
224
+ * it as an intent under the "Other" category drops feature submissions
225
+ * by ~40% in industry studies (Userpilot, Usersnap 2025).
226
+ *
227
+ * Wire format: still routes through the standard `other` category with
228
+ * a `user_category = 'Feature request'` stamp, so we don't need a DB
229
+ * migration. The admin console filters on that string to surface the
230
+ * Feature-request swimlane.
231
+ */
232
+ private renderFeatureRequestEntry;
169
233
  /** Collapsible "What's new" changelog row. Closes the reporter feedback loop. */
170
234
  private renderBetaChangelog;
171
235
  /**
@@ -188,6 +252,19 @@ declare class MushiWidget {
188
252
  * frame instantly.
189
253
  */
190
254
  private renderSuccessStep;
255
+ /**
256
+ * Two-way receipt block. Until the host's `onSubmit` resolves with a
257
+ * server-confirmed report id, we show a discreet "delivering..." pill so
258
+ * the user knows their submission is still in flight. Once we have the
259
+ * id, we surface a short monospaced id + a copy button + an optional
260
+ * "Track on Mushi" deep link to `dashboardUrl/reports/<id>` so the user
261
+ * can watch the status walk through queued -> classified -> fixed in
262
+ * real time (Peak-End rule: the last impression sticks). If we never
263
+ * get an id (offline retry queue), we say so explicitly rather than
264
+ * pretending everything is fine.
265
+ */
266
+ private renderSuccessReceipt;
267
+ private renderSlaLine;
191
268
  /**
192
269
  * Reciprocity footer on the success step: closes the feedback loop by
193
270
  * attributing where the report goes, sets a response expectation, and
@@ -238,20 +315,12 @@ interface NetworkCaptureOptions {
238
315
  declare function createNetworkCapture(options?: NetworkCaptureOptions): NetworkCapture;
239
316
 
240
317
  interface ScreenshotCapture {
241
- take(): Promise<ScreenshotResult>;
318
+ take(): Promise<string | null>;
242
319
  updateOptions(options: ScreenshotCaptureOptions): void;
243
320
  }
244
321
  interface ScreenshotCaptureOptions {
245
322
  privacy?: MushiPrivacyConfig;
246
323
  }
247
- type ScreenshotResult = {
248
- ok: true;
249
- dataUrl: string;
250
- } | {
251
- ok: false;
252
- reason: 'tainted' | 'load-error' | 'unsupported' | 'cancelled' | 'error';
253
- message?: string;
254
- };
255
324
  declare function createScreenshotCapture(options?: ScreenshotCaptureOptions): ScreenshotCapture;
256
325
 
257
326
  interface PerformanceCapture {
@@ -343,6 +412,35 @@ interface ProactiveTriggerConfig {
343
412
  apiCascade?: boolean | MushiApiCascadeConfig;
344
413
  apiEndpoint?: string;
345
414
  errorBoundary?: boolean;
415
+ /**
416
+ * Beta-mode nudges. Fire when the user has been on the same route for
417
+ * `pageDwellMs` continuous milliseconds without filing any report. Default
418
+ * disabled because production apps usually don't want unsolicited prompts;
419
+ * recommended only when `betaMode.enabled` is true on the widget.
420
+ *
421
+ * Pass `true` to use the default 5-minute threshold, or a config object
422
+ * to override. Set to `false` (default) to disable entirely.
423
+ */
424
+ pageDwell?: boolean | {
425
+ thresholdMs?: number;
426
+ excludeRoutes?: string[];
427
+ };
428
+ /**
429
+ * First-session welcome. Fires exactly once per user (tracked via
430
+ * `localStorage`) `delayMs` milliseconds after `Mushi.init`. Use to
431
+ * gently surface the bug button to new beta users so they know feedback
432
+ * is welcome. Default disabled.
433
+ */
434
+ firstSession?: boolean | {
435
+ delayMs?: number;
436
+ storageKey?: string;
437
+ };
438
+ /**
439
+ * The project ID, used to namespace the `firstSession` localStorage key so
440
+ * multi-tenant single-page apps don't share first-session state across
441
+ * projects. Sourced from `MushiConfig.projectId` by the SDK.
442
+ */
443
+ projectId?: string;
346
444
  }
347
445
  interface ProactiveTriggerCallbacks {
348
446
  onTrigger: (type: string, context: Record<string, unknown>) => void;
package/dist/index.d.ts CHANGED
@@ -36,12 +36,27 @@ interface WidgetRewardsState {
36
36
  /** Expected base points for a `report_submit` action (default 50). */
37
37
  pointsForReport: number;
38
38
  }
39
+ interface WidgetSubmitOutcome {
40
+ /** Server-confirmed report id. When `null` the report was queued
41
+ * offline / failed-and-queued for retry; the success step degrades
42
+ * gracefully (no "track on console" link, just the receipt stamp). */
43
+ reportId: string | null;
44
+ /** Convenience flag for the widget to decide whether to render the
45
+ * optimistic copy ("queued offline, we'll send it when you're back")
46
+ * versus the confirmed copy ("received — track at #abc12345"). */
47
+ queuedOffline?: boolean;
48
+ }
39
49
  interface WidgetCallbacks {
50
+ /**
51
+ * Returns the outcome of the submission so the widget can render a
52
+ * real receipt (report id, deep link). Older callers that return
53
+ * `void` still work — the widget falls back to the legacy stamp.
54
+ */
40
55
  onSubmit(data: {
41
56
  category: MushiReportCategory;
42
57
  description: string;
43
58
  intent?: string;
44
- }): void;
59
+ }): void | Promise<WidgetSubmitOutcome | void>;
45
60
  onOpen(): void;
46
61
  onClose(): void;
47
62
  onScreenshotRequest(): void;
@@ -62,6 +77,13 @@ declare class MushiWidget {
62
77
  private step;
63
78
  private selectedCategory;
64
79
  private selectedIntent;
80
+ /**
81
+ * True when the user took the "Feature request" shortcut. We track this
82
+ * separately from `selectedCategory='other'` so the Back button on the
83
+ * details step jumps straight back to the category picker instead of
84
+ * landing on the intent picker the user explicitly skipped.
85
+ */
86
+ private viaFeatureRequest;
65
87
  private screenshotAttached;
66
88
  private screenshotCapturing;
67
89
  private screenshotError;
@@ -99,14 +121,36 @@ declare class MushiWidget {
99
121
  private successTimer;
100
122
  private autoCloseTimer;
101
123
  private rewardsState;
124
+ /** Server-confirmed id for the just-submitted report. Surfaces in
125
+ * the success step as a copyable receipt + optional deep link to
126
+ * the Mushi console (when `dashboardUrl` is configured). Cleared
127
+ * on every new `open()` so a re-opened widget never reuses a
128
+ * stale id from the previous session. */
129
+ private lastReportId;
130
+ /** True when the just-submitted report was queued offline (no
131
+ * network, or the API errored and went into the retry queue).
132
+ * Drives a different success copy so the user knows the report
133
+ * hasn't actually reached the console yet. */
134
+ private lastSubmitQueuedOffline;
135
+ /** Whether the user has clicked ✕ on the header banner this session. */
136
+ private bannerDismissed;
102
137
  constructor(config: MushiWidgetConfig | undefined, callbacks: WidgetCallbacks, sdkVersion?: string);
103
138
  mount(): void;
104
139
  getIsMounted(): boolean;
105
140
  updateConfig(config?: MushiWidgetConfig): void;
106
141
  open(options?: {
107
142
  category?: MushiReportCategory;
143
+ featureRequest?: boolean;
108
144
  }): void;
109
145
  close(): void;
146
+ /**
147
+ * Briefly highlight the trigger button (a soft pulse + tooltip) without
148
+ * opening the full reporter panel. Use for first-session welcome nudges
149
+ * and other "by the way, this exists" prompts where forcing the panel
150
+ * open would feel aggressive. Honours `position: 'none'` (no-op when
151
+ * the trigger button is hidden).
152
+ */
153
+ pulseTrigger(): void;
110
154
  getIsOpen(): boolean;
111
155
  showTrigger(): void;
112
156
  hideTrigger(): void;
@@ -138,6 +182,13 @@ declare class MushiWidget {
138
182
  private syncAttachedLaunchers;
139
183
  private syncSmartHide;
140
184
  private shouldRenderTrigger;
185
+ /** Height of the banner in px — kept in sync with the CSS `.mushi-banner` height (36px). */
186
+ private static readonly BANNER_HEIGHT;
187
+ /** CSS property applied to document.body so host-app content doesn't slide under the banner. */
188
+ private static readonly BODY_NUDGE_PROP;
189
+ private applyBodyNudge;
190
+ private removeBodyNudge;
191
+ private renderBanner;
141
192
  private effectiveTrigger;
142
193
  private isMobileSmartHidden;
143
194
  private detectEnvironment;
@@ -166,6 +217,19 @@ declare class MushiWidget {
166
217
  */
167
218
  private renderStepIndicator;
168
219
  private renderCategoryStep;
220
+ /**
221
+ * First-class "Feature request" entry rendered at the top of the
222
+ * category step. Beta apps consistently get more useful signal when
223
+ * the user has a no-friction path to say "I wish this did X" — burying
224
+ * it as an intent under the "Other" category drops feature submissions
225
+ * by ~40% in industry studies (Userpilot, Usersnap 2025).
226
+ *
227
+ * Wire format: still routes through the standard `other` category with
228
+ * a `user_category = 'Feature request'` stamp, so we don't need a DB
229
+ * migration. The admin console filters on that string to surface the
230
+ * Feature-request swimlane.
231
+ */
232
+ private renderFeatureRequestEntry;
169
233
  /** Collapsible "What's new" changelog row. Closes the reporter feedback loop. */
170
234
  private renderBetaChangelog;
171
235
  /**
@@ -188,6 +252,19 @@ declare class MushiWidget {
188
252
  * frame instantly.
189
253
  */
190
254
  private renderSuccessStep;
255
+ /**
256
+ * Two-way receipt block. Until the host's `onSubmit` resolves with a
257
+ * server-confirmed report id, we show a discreet "delivering..." pill so
258
+ * the user knows their submission is still in flight. Once we have the
259
+ * id, we surface a short monospaced id + a copy button + an optional
260
+ * "Track on Mushi" deep link to `dashboardUrl/reports/<id>` so the user
261
+ * can watch the status walk through queued -> classified -> fixed in
262
+ * real time (Peak-End rule: the last impression sticks). If we never
263
+ * get an id (offline retry queue), we say so explicitly rather than
264
+ * pretending everything is fine.
265
+ */
266
+ private renderSuccessReceipt;
267
+ private renderSlaLine;
191
268
  /**
192
269
  * Reciprocity footer on the success step: closes the feedback loop by
193
270
  * attributing where the report goes, sets a response expectation, and
@@ -238,20 +315,12 @@ interface NetworkCaptureOptions {
238
315
  declare function createNetworkCapture(options?: NetworkCaptureOptions): NetworkCapture;
239
316
 
240
317
  interface ScreenshotCapture {
241
- take(): Promise<ScreenshotResult>;
318
+ take(): Promise<string | null>;
242
319
  updateOptions(options: ScreenshotCaptureOptions): void;
243
320
  }
244
321
  interface ScreenshotCaptureOptions {
245
322
  privacy?: MushiPrivacyConfig;
246
323
  }
247
- type ScreenshotResult = {
248
- ok: true;
249
- dataUrl: string;
250
- } | {
251
- ok: false;
252
- reason: 'tainted' | 'load-error' | 'unsupported' | 'cancelled' | 'error';
253
- message?: string;
254
- };
255
324
  declare function createScreenshotCapture(options?: ScreenshotCaptureOptions): ScreenshotCapture;
256
325
 
257
326
  interface PerformanceCapture {
@@ -343,6 +412,35 @@ interface ProactiveTriggerConfig {
343
412
  apiCascade?: boolean | MushiApiCascadeConfig;
344
413
  apiEndpoint?: string;
345
414
  errorBoundary?: boolean;
415
+ /**
416
+ * Beta-mode nudges. Fire when the user has been on the same route for
417
+ * `pageDwellMs` continuous milliseconds without filing any report. Default
418
+ * disabled because production apps usually don't want unsolicited prompts;
419
+ * recommended only when `betaMode.enabled` is true on the widget.
420
+ *
421
+ * Pass `true` to use the default 5-minute threshold, or a config object
422
+ * to override. Set to `false` (default) to disable entirely.
423
+ */
424
+ pageDwell?: boolean | {
425
+ thresholdMs?: number;
426
+ excludeRoutes?: string[];
427
+ };
428
+ /**
429
+ * First-session welcome. Fires exactly once per user (tracked via
430
+ * `localStorage`) `delayMs` milliseconds after `Mushi.init`. Use to
431
+ * gently surface the bug button to new beta users so they know feedback
432
+ * is welcome. Default disabled.
433
+ */
434
+ firstSession?: boolean | {
435
+ delayMs?: number;
436
+ storageKey?: string;
437
+ };
438
+ /**
439
+ * The project ID, used to namespace the `firstSession` localStorage key so
440
+ * multi-tenant single-page apps don't share first-session state across
441
+ * projects. Sourced from `MushiConfig.projectId` by the SDK.
442
+ */
443
+ projectId?: string;
346
444
  }
347
445
  interface ProactiveTriggerCallbacks {
348
446
  onTrigger: (type: string, context: Record<string, unknown>) => void;