@mushi-mushi/core 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
@@ -19,43 +19,12 @@ interface MushiConfig {
19
19
  integrations?: MushiIntegrationsConfig;
20
20
  offline?: MushiOfflineConfig;
21
21
  rewards?: MushiRewardsConfig;
22
- /**
23
- * Sentry-spec-1.0 hook fired AFTER preFilter / on-device classifier /
24
- * rate-limit gates pass and BEFORE the report is sent or queued.
25
- * Return:
26
- * - the report (possibly modified) → submit as-is
27
- * - a Promise<MushiReport> → await, then submit
28
- * - `null` → drop the report silently (no `report:sent`/`:failed` event)
29
- *
30
- * Use this for app-level redaction of sensitive metadata, hard-coded
31
- * tag overrides, or last-mile category routing. Errors thrown inside
32
- * the hook are caught and logged; the report still ships unchanged so
33
- * a buggy hook never silently swallows feedback.
34
- *
35
- * Mirrors Sentry's [SDK feedback spec §4](https://develop.sentry.dev/sdk/telemetry/feedbacks/)
36
- * `beforeSendFeedback` callback.
37
- */
38
- beforeSendFeedback?: (report: MushiReport) => MushiReport | Promise<MushiReport | null> | null;
39
- /**
40
- * Sentry-spec-1.0 callback fired exactly once on `init()` after the
41
- * SDK detects that the previous browser session ended in a crash
42
- * (uncaught exception, unhandled rejection, or hard navigate during
43
- * unfinished submit). Use it to surface a "Tell us what went wrong?"
44
- * prompt to the user — the SDK does NOT auto-open the widget so the
45
- * host app can decide on copy and timing.
46
- *
47
- * The promise resolves with `true` when a crash was detected, `false`
48
- * when the previous run ended cleanly, and `null` when the SDK can't
49
- * tell (typically: localStorage unavailable, or first-ever load).
50
- *
51
- * Mirrors Sentry's [SDK feedback spec §6](https://develop.sentry.dev/sdk/telemetry/feedbacks/)
52
- * `onCrashedLastRun` hook.
53
- */
54
- onCrashedLastRun?: (info: {
55
- crashed: boolean | null;
56
- }) => void;
57
22
  debug?: boolean;
58
23
  enabled?: boolean;
24
+ /** Hook called before a report is sent. Return null to cancel, or return the (possibly modified) report. */
25
+ beforeSendFeedback?: (report: MushiReport) => MushiReport | null | Promise<MushiReport | null>;
26
+ /** Called once if the app crashed during the previous session. */
27
+ onCrashedLastRun?: (crashed: boolean) => void;
59
28
  }
60
29
  interface MushiSentryConfig {
61
30
  dsn?: string;
@@ -78,9 +47,17 @@ interface MushiWidgetConfig {
78
47
  zIndex?: number;
79
48
  /**
80
49
  * Controls how, or whether, the default trigger is injected.
81
- * `auto` preserves the historical floating stamp button.
50
+ * `auto` historical floating stamp FAB.
51
+ * `banner` — slim full-width header strip (recommended default; less obtrusive
52
+ * than a FAB and visible even when the SDK launcher is `hide()`'d).
53
+ * Pair with `bannerConfig` to customise appearance.
54
+ */
55
+ trigger?: 'auto' | 'banner' | 'edge-tab' | 'attach' | 'manual' | 'hidden';
56
+ /**
57
+ * Configuration for the header-banner launcher mode.
58
+ * Only applies when `trigger === 'banner'`.
82
59
  */
83
- trigger?: 'auto' | 'edge-tab' | 'attach' | 'manual' | 'hidden';
60
+ bannerConfig?: MushiBannerConfig;
84
61
  /** CSS selector used when `trigger` is `attach`. */
85
62
  attachToSelector?: string;
86
63
  /**
@@ -109,13 +86,65 @@ interface MushiWidgetConfig {
109
86
  */
110
87
  betaMode?: MushiBetaModeConfig;
111
88
  /**
112
- * Minimum description length (characters) required before the user can submit.
113
- * Overrides the SDK default of 20. The widget halves this automatically for
114
- * CJK locales (ja/zh/ko) where a short string carries more semantic content
115
- * than in English. Forwarded from `preFilter.minDescriptionLength` if unset.
89
+ * Absolute base URL of the Mushi admin console (e.g. `https://mushi.example.com`).
90
+ * When set, the success step surfaces a one-tap link to the user's own report
91
+ * on the console so they can watch the status change in real time. Without
92
+ * this the success step still confirms submission but cannot deep-link.
93
+ *
94
+ * This is intentionally separate from the API endpoint — production apps
95
+ * usually have the API on `api.mushi.example.com` and the console on
96
+ * `app.mushi.example.com`.
97
+ */
98
+ dashboardUrl?: string;
99
+ /**
100
+ * Override the SLA copy shown in the success step's "what happens next"
101
+ * line. Defaults to "We aim to review within 48h". Set to an empty string
102
+ * to hide the line entirely (e.g. internal-only deployments where SLA
103
+ * messaging would be over-promising).
104
+ */
105
+ responseSlaLabel?: string;
106
+ /**
107
+ * Show a first-class "Feature request" card at the top of the category
108
+ * step. Defaults to true. Set to false for production-only deployments
109
+ * where you don't want to invite feature ideas through the widget.
110
+ * Internally this maps to `category='other'` with
111
+ * `user_category='Feature request'` so no DB migration is needed.
116
112
  */
113
+ featureRequestCard?: boolean;
114
+ /** Override the localised label for the feature-request card. */
115
+ featureRequestLabel?: string;
116
+ /** Override the helper text shown under the feature-request card. */
117
+ featureRequestDescription?: string;
118
+ /** Minimum description character count before the submit button enables. */
117
119
  minDescriptionLength?: number;
118
120
  }
121
+ /**
122
+ * Configuration for the `trigger: 'banner'` header-strip launcher.
123
+ *
124
+ * The banner renders as a slim, full-width strip pinned to the top of the
125
+ * viewport (or bottom if `position === 'bottom'`). It is styled to match the
126
+ * app's brand accent and dismissed per-session via a ✕ button.
127
+ *
128
+ * Variants
129
+ * --------
130
+ * `neon` — lime / electric-green strip (high contrast, dev / beta tool feel).
131
+ * `brand` — uses the Mushi vermillion accent (editorial, app-quality feel).
132
+ * `subtle` — near-invisible hairline with muted text (least disruptive).
133
+ */
134
+ interface MushiBannerConfig {
135
+ /** Visual style of the banner strip. Defaults to `'brand'`. */
136
+ variant?: 'neon' | 'brand' | 'subtle';
137
+ /** 'top' pins the banner below any existing sticky headers; 'bottom' pins above bottom navs. Defaults to 'top'. */
138
+ position?: 'top' | 'bottom';
139
+ /** Override the call-to-action text in the banner. Defaults to 'Report a bug'. */
140
+ bugCta?: string;
141
+ /** Show a "✨ Request a feature" button alongside the bug button. Defaults to true. */
142
+ featureCta?: boolean;
143
+ /** Override the feature-request button label. */
144
+ featureCtaLabel?: string;
145
+ /** CSS z-index of the banner element. Defaults to the widget's configured zIndex. */
146
+ zIndex?: number;
147
+ }
119
148
  interface MushiBetaModeConfig {
120
149
  enabled?: boolean;
121
150
  /** Display name of the app shown in the beta strip. Defaults to 'This app'. */
@@ -240,6 +269,17 @@ interface MushiPrivacyConfig {
240
269
  maskSelectors?: string[];
241
270
  /** DOM subtrees to remove from screenshots before upload. */
242
271
  blockSelectors?: string[];
272
+ /**
273
+ * CSS selectors whose matching elements are blacked-out (filled with an
274
+ * opaque black rectangle) in screenshots before upload. Intended for
275
+ * sensitive fields that should never appear in any form — passwords, PII,
276
+ * financial data. Applied in addition to `maskSelectors`.
277
+ *
278
+ * Default: `['input[type="password"]', '[data-mushi-redact]']`
279
+ *
280
+ * To disable the default redaction, pass an empty array.
281
+ */
282
+ redactSelectors?: string[];
243
283
  /** Let reporters remove an attached screenshot before submitting. Defaults to true. */
244
284
  allowUserRemoveScreenshot?: boolean;
245
285
  }
@@ -249,6 +289,38 @@ interface MushiProactiveConfig {
249
289
  longTask?: boolean;
250
290
  apiCascade?: boolean | MushiApiCascadeConfig;
251
291
  cooldown?: MushiCooldownConfig;
292
+ /**
293
+ * Beta-mode nudge: fire after the user has been on the same route for
294
+ * `thresholdMs` continuous milliseconds (default 5min). Pass `true` to
295
+ * accept the default threshold, or a config object to override. Use
296
+ * conservatively — set the per-session cap in `cooldown` to avoid
297
+ * nag fatigue.
298
+ */
299
+ pageDwell?: boolean | MushiPageDwellConfig;
300
+ /**
301
+ * One-shot welcome prompt for first-time visitors. Fires `delayMs` after
302
+ * `Mushi.init` (default 45s) and is suppressed permanently after the
303
+ * first fire via localStorage. Recommended for beta deployments.
304
+ */
305
+ firstSession?: boolean | MushiFirstSessionConfig;
306
+ }
307
+ interface MushiPageDwellConfig {
308
+ /** Continuous dwell time before firing. Defaults to 5 minutes. */
309
+ thresholdMs?: number;
310
+ /**
311
+ * Route path prefixes (or glob-style patterns with `*`) that suppress the
312
+ * dwell nudge. Auth routes are excluded by default so users aren't prompted
313
+ * during login/signup flows.
314
+ *
315
+ * Default: `['/login', '/logout', '/signup', '/sso/*', '/auth/*']`
316
+ */
317
+ excludeRoutes?: string[];
318
+ }
319
+ interface MushiFirstSessionConfig {
320
+ /** Delay before firing the welcome nudge. Defaults to 45 seconds. */
321
+ delayMs?: number;
322
+ /** Override the localStorage key used to mark the user as welcomed. */
323
+ storageKey?: string;
252
324
  }
253
325
  type MushiUrlMatcher = string | RegExp;
254
326
  interface MushiApiCascadeConfig {
@@ -704,32 +776,16 @@ interface MushiPerformanceMetrics {
704
776
  lcp?: number;
705
777
  cls?: number;
706
778
  fid?: number;
707
- /**
708
- * Interaction to Next Paint — the worst-observed user interaction
709
- * latency (ms) since SDK init. Replaces FID as a Core Web Vital
710
- * since March 2024. Captured via `PerformanceObserver({ type: 'event',
711
- * durationThreshold: 40 })` per the [web-vitals INP spec](https://web.dev/articles/inp).
712
- */
713
779
  inp?: number;
714
- /**
715
- * Optional INP attribution — captured from the worst-observed
716
- * interaction. Lets the triage UI surface "the slow click was on
717
- * <button.checkout>" rather than just "1200 ms INP".
718
- */
780
+ ttfb?: number;
781
+ longTasks?: number;
719
782
  inpAttribution?: {
720
- /** PerformanceEventTiming.name (`pointerdown`, `keydown`, …). */
721
783
  eventType?: string;
722
- /** Tag + id + first class of the element that triggered the slow event. */
723
784
  targetSelector?: string;
724
- /** Time between the user input and the start of event processing. */
725
785
  inputDelay?: number;
726
- /** Time spent running the event handler. */
727
786
  processingDuration?: number;
728
- /** Time between handler end and the next paint. */
729
787
  presentationDelay?: number;
730
788
  };
731
- ttfb?: number;
732
- longTasks?: number;
733
789
  }
734
790
  interface MushiSelectedElement {
735
791
  tagName: string;
@@ -762,14 +818,7 @@ interface MushiTimelineEntry {
762
818
  kind: MushiTimelineKind;
763
819
  payload: Record<string, unknown>;
764
820
  }
765
- type MushiEventType = 'report:submitted' | 'report:queued' | 'report:sent' | 'report:failed'
766
- /**
767
- * Fired when the submitted report has been picked up by a Cursor Cloud
768
- * Agent and an automated fix is in progress. `data.agentId` is the
769
- * Cursor agent run ID (bc-…); `data.fixId` is the mushi fix_attempt UUID.
770
- * Useful for showing a toast: "A Cursor agent is working on your report".
771
- */
772
- | 'report:dispatched' | 'widget:opened' | 'widget:closed' | 'proactive:triggered' | 'proactive:dismissed';
821
+ type MushiEventType = 'report:submitted' | 'report:queued' | 'report:sent' | 'report:failed' | 'widget:opened' | 'widget:closed' | 'proactive:triggered' | 'proactive:dismissed' | 'report:dispatched';
773
822
  type MushiEventHandler = (event: {
774
823
  type: MushiEventType;
775
824
  data?: unknown;
@@ -786,8 +835,16 @@ interface MushiDiagnosticsResult {
786
835
  sdkVersion: string;
787
836
  }
788
837
  interface MushiSDKInstance {
838
+ /**
839
+ * Open the reporter widget. With no options, opens to the category
840
+ * picker so the user can choose between bug categories and the
841
+ * feature-request shortcut. Pass `{ category }` to deep-link into a
842
+ * specific bug intent, or `{ featureRequest: true }` to deep-link into
843
+ * the feature-request description step (skips intent picker).
844
+ */
789
845
  report(options?: {
790
846
  category?: MushiReportCategory;
847
+ featureRequest?: boolean;
791
848
  }): void;
792
849
  on(event: MushiEventType, handler: MushiEventHandler): () => void;
793
850
  setUser(user: {
@@ -888,6 +945,12 @@ interface MushiSDKInstance {
888
945
  * No-op when rewards are disabled or the user has not opted in.
889
946
  */
890
947
  recordActivity(action: string, metadata?: Record<string, unknown>): void;
948
+ /**
949
+ * Briefly animate the bug-report trigger button to draw the user's
950
+ * attention without opening the full widget. Ideal for subtle "feedback
951
+ * welcome" nudges (first-session, beta-onboarding).
952
+ */
953
+ pulseTrigger(): void;
891
954
  }
892
955
  interface MushiCaptureExceptionOptions {
893
956
  /** Override the default `'bug'` category (e.g. `'slow'` for timeouts). */
@@ -1339,4 +1402,4 @@ declare function createLogger(options: LoggerOptions): Logger;
1339
1402
  */
1340
1403
  declare const noopLogger: Logger;
1341
1404
 
1342
- export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, type MushiActivityEvent, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, normaliseThrown, resolveRegionEndpoint, scrubPii };
1405
+ export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, type MushiActivityEvent, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiBannerConfig, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, normaliseThrown, resolveRegionEndpoint, scrubPii };
package/dist/index.d.ts CHANGED
@@ -19,43 +19,12 @@ interface MushiConfig {
19
19
  integrations?: MushiIntegrationsConfig;
20
20
  offline?: MushiOfflineConfig;
21
21
  rewards?: MushiRewardsConfig;
22
- /**
23
- * Sentry-spec-1.0 hook fired AFTER preFilter / on-device classifier /
24
- * rate-limit gates pass and BEFORE the report is sent or queued.
25
- * Return:
26
- * - the report (possibly modified) → submit as-is
27
- * - a Promise<MushiReport> → await, then submit
28
- * - `null` → drop the report silently (no `report:sent`/`:failed` event)
29
- *
30
- * Use this for app-level redaction of sensitive metadata, hard-coded
31
- * tag overrides, or last-mile category routing. Errors thrown inside
32
- * the hook are caught and logged; the report still ships unchanged so
33
- * a buggy hook never silently swallows feedback.
34
- *
35
- * Mirrors Sentry's [SDK feedback spec §4](https://develop.sentry.dev/sdk/telemetry/feedbacks/)
36
- * `beforeSendFeedback` callback.
37
- */
38
- beforeSendFeedback?: (report: MushiReport) => MushiReport | Promise<MushiReport | null> | null;
39
- /**
40
- * Sentry-spec-1.0 callback fired exactly once on `init()` after the
41
- * SDK detects that the previous browser session ended in a crash
42
- * (uncaught exception, unhandled rejection, or hard navigate during
43
- * unfinished submit). Use it to surface a "Tell us what went wrong?"
44
- * prompt to the user — the SDK does NOT auto-open the widget so the
45
- * host app can decide on copy and timing.
46
- *
47
- * The promise resolves with `true` when a crash was detected, `false`
48
- * when the previous run ended cleanly, and `null` when the SDK can't
49
- * tell (typically: localStorage unavailable, or first-ever load).
50
- *
51
- * Mirrors Sentry's [SDK feedback spec §6](https://develop.sentry.dev/sdk/telemetry/feedbacks/)
52
- * `onCrashedLastRun` hook.
53
- */
54
- onCrashedLastRun?: (info: {
55
- crashed: boolean | null;
56
- }) => void;
57
22
  debug?: boolean;
58
23
  enabled?: boolean;
24
+ /** Hook called before a report is sent. Return null to cancel, or return the (possibly modified) report. */
25
+ beforeSendFeedback?: (report: MushiReport) => MushiReport | null | Promise<MushiReport | null>;
26
+ /** Called once if the app crashed during the previous session. */
27
+ onCrashedLastRun?: (crashed: boolean) => void;
59
28
  }
60
29
  interface MushiSentryConfig {
61
30
  dsn?: string;
@@ -78,9 +47,17 @@ interface MushiWidgetConfig {
78
47
  zIndex?: number;
79
48
  /**
80
49
  * Controls how, or whether, the default trigger is injected.
81
- * `auto` preserves the historical floating stamp button.
50
+ * `auto` historical floating stamp FAB.
51
+ * `banner` — slim full-width header strip (recommended default; less obtrusive
52
+ * than a FAB and visible even when the SDK launcher is `hide()`'d).
53
+ * Pair with `bannerConfig` to customise appearance.
54
+ */
55
+ trigger?: 'auto' | 'banner' | 'edge-tab' | 'attach' | 'manual' | 'hidden';
56
+ /**
57
+ * Configuration for the header-banner launcher mode.
58
+ * Only applies when `trigger === 'banner'`.
82
59
  */
83
- trigger?: 'auto' | 'edge-tab' | 'attach' | 'manual' | 'hidden';
60
+ bannerConfig?: MushiBannerConfig;
84
61
  /** CSS selector used when `trigger` is `attach`. */
85
62
  attachToSelector?: string;
86
63
  /**
@@ -109,13 +86,65 @@ interface MushiWidgetConfig {
109
86
  */
110
87
  betaMode?: MushiBetaModeConfig;
111
88
  /**
112
- * Minimum description length (characters) required before the user can submit.
113
- * Overrides the SDK default of 20. The widget halves this automatically for
114
- * CJK locales (ja/zh/ko) where a short string carries more semantic content
115
- * than in English. Forwarded from `preFilter.minDescriptionLength` if unset.
89
+ * Absolute base URL of the Mushi admin console (e.g. `https://mushi.example.com`).
90
+ * When set, the success step surfaces a one-tap link to the user's own report
91
+ * on the console so they can watch the status change in real time. Without
92
+ * this the success step still confirms submission but cannot deep-link.
93
+ *
94
+ * This is intentionally separate from the API endpoint — production apps
95
+ * usually have the API on `api.mushi.example.com` and the console on
96
+ * `app.mushi.example.com`.
97
+ */
98
+ dashboardUrl?: string;
99
+ /**
100
+ * Override the SLA copy shown in the success step's "what happens next"
101
+ * line. Defaults to "We aim to review within 48h". Set to an empty string
102
+ * to hide the line entirely (e.g. internal-only deployments where SLA
103
+ * messaging would be over-promising).
104
+ */
105
+ responseSlaLabel?: string;
106
+ /**
107
+ * Show a first-class "Feature request" card at the top of the category
108
+ * step. Defaults to true. Set to false for production-only deployments
109
+ * where you don't want to invite feature ideas through the widget.
110
+ * Internally this maps to `category='other'` with
111
+ * `user_category='Feature request'` so no DB migration is needed.
116
112
  */
113
+ featureRequestCard?: boolean;
114
+ /** Override the localised label for the feature-request card. */
115
+ featureRequestLabel?: string;
116
+ /** Override the helper text shown under the feature-request card. */
117
+ featureRequestDescription?: string;
118
+ /** Minimum description character count before the submit button enables. */
117
119
  minDescriptionLength?: number;
118
120
  }
121
+ /**
122
+ * Configuration for the `trigger: 'banner'` header-strip launcher.
123
+ *
124
+ * The banner renders as a slim, full-width strip pinned to the top of the
125
+ * viewport (or bottom if `position === 'bottom'`). It is styled to match the
126
+ * app's brand accent and dismissed per-session via a ✕ button.
127
+ *
128
+ * Variants
129
+ * --------
130
+ * `neon` — lime / electric-green strip (high contrast, dev / beta tool feel).
131
+ * `brand` — uses the Mushi vermillion accent (editorial, app-quality feel).
132
+ * `subtle` — near-invisible hairline with muted text (least disruptive).
133
+ */
134
+ interface MushiBannerConfig {
135
+ /** Visual style of the banner strip. Defaults to `'brand'`. */
136
+ variant?: 'neon' | 'brand' | 'subtle';
137
+ /** 'top' pins the banner below any existing sticky headers; 'bottom' pins above bottom navs. Defaults to 'top'. */
138
+ position?: 'top' | 'bottom';
139
+ /** Override the call-to-action text in the banner. Defaults to 'Report a bug'. */
140
+ bugCta?: string;
141
+ /** Show a "✨ Request a feature" button alongside the bug button. Defaults to true. */
142
+ featureCta?: boolean;
143
+ /** Override the feature-request button label. */
144
+ featureCtaLabel?: string;
145
+ /** CSS z-index of the banner element. Defaults to the widget's configured zIndex. */
146
+ zIndex?: number;
147
+ }
119
148
  interface MushiBetaModeConfig {
120
149
  enabled?: boolean;
121
150
  /** Display name of the app shown in the beta strip. Defaults to 'This app'. */
@@ -240,6 +269,17 @@ interface MushiPrivacyConfig {
240
269
  maskSelectors?: string[];
241
270
  /** DOM subtrees to remove from screenshots before upload. */
242
271
  blockSelectors?: string[];
272
+ /**
273
+ * CSS selectors whose matching elements are blacked-out (filled with an
274
+ * opaque black rectangle) in screenshots before upload. Intended for
275
+ * sensitive fields that should never appear in any form — passwords, PII,
276
+ * financial data. Applied in addition to `maskSelectors`.
277
+ *
278
+ * Default: `['input[type="password"]', '[data-mushi-redact]']`
279
+ *
280
+ * To disable the default redaction, pass an empty array.
281
+ */
282
+ redactSelectors?: string[];
243
283
  /** Let reporters remove an attached screenshot before submitting. Defaults to true. */
244
284
  allowUserRemoveScreenshot?: boolean;
245
285
  }
@@ -249,6 +289,38 @@ interface MushiProactiveConfig {
249
289
  longTask?: boolean;
250
290
  apiCascade?: boolean | MushiApiCascadeConfig;
251
291
  cooldown?: MushiCooldownConfig;
292
+ /**
293
+ * Beta-mode nudge: fire after the user has been on the same route for
294
+ * `thresholdMs` continuous milliseconds (default 5min). Pass `true` to
295
+ * accept the default threshold, or a config object to override. Use
296
+ * conservatively — set the per-session cap in `cooldown` to avoid
297
+ * nag fatigue.
298
+ */
299
+ pageDwell?: boolean | MushiPageDwellConfig;
300
+ /**
301
+ * One-shot welcome prompt for first-time visitors. Fires `delayMs` after
302
+ * `Mushi.init` (default 45s) and is suppressed permanently after the
303
+ * first fire via localStorage. Recommended for beta deployments.
304
+ */
305
+ firstSession?: boolean | MushiFirstSessionConfig;
306
+ }
307
+ interface MushiPageDwellConfig {
308
+ /** Continuous dwell time before firing. Defaults to 5 minutes. */
309
+ thresholdMs?: number;
310
+ /**
311
+ * Route path prefixes (or glob-style patterns with `*`) that suppress the
312
+ * dwell nudge. Auth routes are excluded by default so users aren't prompted
313
+ * during login/signup flows.
314
+ *
315
+ * Default: `['/login', '/logout', '/signup', '/sso/*', '/auth/*']`
316
+ */
317
+ excludeRoutes?: string[];
318
+ }
319
+ interface MushiFirstSessionConfig {
320
+ /** Delay before firing the welcome nudge. Defaults to 45 seconds. */
321
+ delayMs?: number;
322
+ /** Override the localStorage key used to mark the user as welcomed. */
323
+ storageKey?: string;
252
324
  }
253
325
  type MushiUrlMatcher = string | RegExp;
254
326
  interface MushiApiCascadeConfig {
@@ -704,32 +776,16 @@ interface MushiPerformanceMetrics {
704
776
  lcp?: number;
705
777
  cls?: number;
706
778
  fid?: number;
707
- /**
708
- * Interaction to Next Paint — the worst-observed user interaction
709
- * latency (ms) since SDK init. Replaces FID as a Core Web Vital
710
- * since March 2024. Captured via `PerformanceObserver({ type: 'event',
711
- * durationThreshold: 40 })` per the [web-vitals INP spec](https://web.dev/articles/inp).
712
- */
713
779
  inp?: number;
714
- /**
715
- * Optional INP attribution — captured from the worst-observed
716
- * interaction. Lets the triage UI surface "the slow click was on
717
- * <button.checkout>" rather than just "1200 ms INP".
718
- */
780
+ ttfb?: number;
781
+ longTasks?: number;
719
782
  inpAttribution?: {
720
- /** PerformanceEventTiming.name (`pointerdown`, `keydown`, …). */
721
783
  eventType?: string;
722
- /** Tag + id + first class of the element that triggered the slow event. */
723
784
  targetSelector?: string;
724
- /** Time between the user input and the start of event processing. */
725
785
  inputDelay?: number;
726
- /** Time spent running the event handler. */
727
786
  processingDuration?: number;
728
- /** Time between handler end and the next paint. */
729
787
  presentationDelay?: number;
730
788
  };
731
- ttfb?: number;
732
- longTasks?: number;
733
789
  }
734
790
  interface MushiSelectedElement {
735
791
  tagName: string;
@@ -762,14 +818,7 @@ interface MushiTimelineEntry {
762
818
  kind: MushiTimelineKind;
763
819
  payload: Record<string, unknown>;
764
820
  }
765
- type MushiEventType = 'report:submitted' | 'report:queued' | 'report:sent' | 'report:failed'
766
- /**
767
- * Fired when the submitted report has been picked up by a Cursor Cloud
768
- * Agent and an automated fix is in progress. `data.agentId` is the
769
- * Cursor agent run ID (bc-…); `data.fixId` is the mushi fix_attempt UUID.
770
- * Useful for showing a toast: "A Cursor agent is working on your report".
771
- */
772
- | 'report:dispatched' | 'widget:opened' | 'widget:closed' | 'proactive:triggered' | 'proactive:dismissed';
821
+ type MushiEventType = 'report:submitted' | 'report:queued' | 'report:sent' | 'report:failed' | 'widget:opened' | 'widget:closed' | 'proactive:triggered' | 'proactive:dismissed' | 'report:dispatched';
773
822
  type MushiEventHandler = (event: {
774
823
  type: MushiEventType;
775
824
  data?: unknown;
@@ -786,8 +835,16 @@ interface MushiDiagnosticsResult {
786
835
  sdkVersion: string;
787
836
  }
788
837
  interface MushiSDKInstance {
838
+ /**
839
+ * Open the reporter widget. With no options, opens to the category
840
+ * picker so the user can choose between bug categories and the
841
+ * feature-request shortcut. Pass `{ category }` to deep-link into a
842
+ * specific bug intent, or `{ featureRequest: true }` to deep-link into
843
+ * the feature-request description step (skips intent picker).
844
+ */
789
845
  report(options?: {
790
846
  category?: MushiReportCategory;
847
+ featureRequest?: boolean;
791
848
  }): void;
792
849
  on(event: MushiEventType, handler: MushiEventHandler): () => void;
793
850
  setUser(user: {
@@ -888,6 +945,12 @@ interface MushiSDKInstance {
888
945
  * No-op when rewards are disabled or the user has not opted in.
889
946
  */
890
947
  recordActivity(action: string, metadata?: Record<string, unknown>): void;
948
+ /**
949
+ * Briefly animate the bug-report trigger button to draw the user's
950
+ * attention without opening the full widget. Ideal for subtle "feedback
951
+ * welcome" nudges (first-session, beta-onboarding).
952
+ */
953
+ pulseTrigger(): void;
891
954
  }
892
955
  interface MushiCaptureExceptionOptions {
893
956
  /** Override the default `'bug'` category (e.g. `'slow'` for timeouts). */
@@ -1339,4 +1402,4 @@ declare function createLogger(options: LoggerOptions): Logger;
1339
1402
  */
1340
1403
  declare const noopLogger: Logger;
1341
1404
 
1342
- export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, type MushiActivityEvent, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, normaliseThrown, resolveRegionEndpoint, scrubPii };
1405
+ export { type ApiClientOptions, type BreadcrumbBuffer, type BreadcrumbBufferOptions, DEFAULT_API_ENDPOINT, type LogEntry, type LogFormat, type LogLevel, type Logger, type LoggerOptions, MUSHI_INTERNAL_HEADER, MUSHI_INTERNAL_INIT_MARKER, type MushiActivityEvent, type MushiApiCascadeConfig, type MushiApiClient, type MushiApiResponse, type MushiBannerConfig, type MushiBetaChangelogEntry, type MushiBetaModeConfig, type MushiBreadcrumb, type MushiCaptureConfig, type MushiCaptureEventInput, type MushiCaptureExceptionOptions, type MushiConfig, type MushiConsoleEntry, type MushiCooldownConfig, type MushiDiagnosticsResult, type MushiDiscoverInventoryConfig, type MushiDiscoveryEventPayload, type MushiEnvironment, type MushiEventHandler, type MushiEventType, type MushiIntegrationsConfig, type MushiInternalRequestKind, type MushiNetworkEntry, type MushiOfflineConfig, type MushiOnDeviceClassifier, type MushiOnDeviceClassifierInput, type MushiOnDeviceClassifierResult, type MushiPerformanceMetrics, type MushiPreFilterConfig, type MushiPreset, type MushiPrivacyConfig, type MushiProactiveConfig, type MushiRegion, type MushiReport, type MushiReportBuilder, type MushiReportCategory, type MushiReportStatus, type MushiReporterComment, type MushiReporterReport, type MushiReputationResult, type MushiRewardsConfig, type MushiRuntimeSdkConfig, type MushiSDKInstance, type MushiSdkVersionInfo, type MushiSelectedElement, type MushiSentryConfig, type MushiSentryContext, type MushiTierResult, type MushiTimelineEntry, type MushiTimelineKind, type MushiUrlMatcher, type MushiWidgetAnchor, type MushiWidgetConfig, type NormalisedException, type OfflineQueue, type PiiScrubberConfig, type PreFilterResult, REGION_ENDPOINTS, type RateLimiter, type RateLimiterConfig, captureEnvironment, createApiClient, createBreadcrumbBuffer, createLogger, createOfflineQueue, createPiiScrubber, createPreFilter, createRateLimiter, getDeviceFingerprintHash, getReporterToken, getSessionId, noopLogger, normaliseThrown, resolveRegionEndpoint, scrubPii };
package/dist/index.js CHANGED
@@ -50,7 +50,7 @@ function createApiClient(options) {
50
50
  return {
51
51
  ok: false,
52
52
  error: {
53
- code: `HTTP_${response.status}`,
53
+ code: errorBody.error?.code || `HTTP_${response.status}`,
54
54
  message: errorBody.error?.message || errorBody.message || `HTTP ${response.status} error`
55
55
  }
56
56
  };
@@ -775,6 +775,17 @@ function createOfflineQueue(config = {}) {
775
775
  }
776
776
  sent++;
777
777
  } else {
778
+ const permanent = result.error?.code === "HTTP_400" || result.error?.code === "HTTP_422" || result.error?.code === "INGEST_ERROR" || result.error?.code === "VALIDATION_ERROR" || typeof result.error?.message === "string" && /invalid payload|description must be at least|validation/i.test(
779
+ result.error.message
780
+ );
781
+ if (permanent) {
782
+ try {
783
+ if (backend === "indexeddb") await idbDelete(rowId);
784
+ else lsDelete(rowId);
785
+ } catch {
786
+ lsDelete(rowId);
787
+ }
788
+ }
778
789
  failed++;
779
790
  if (i < batch.length - 1) {
780
791
  await sleep2(getBackoffDelay2(i));