@mushi-mushi/web 1.3.0 → 1.5.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.js CHANGED
@@ -3088,6 +3088,15 @@ function maskElement(el) {
3088
3088
  }
3089
3089
 
3090
3090
  // src/capture/performance.ts
3091
+ var INP_DURATION_THRESHOLD_MS = 40;
3092
+ function describeElement(target) {
3093
+ if (!target || !target.tagName) return void 0;
3094
+ const el = target;
3095
+ const tag = el.tagName.toLowerCase();
3096
+ const id = el.id ? `#${el.id}` : "";
3097
+ const cls = el.classList && el.classList.length > 0 ? `.${el.classList[0]}` : "";
3098
+ return `${tag}${id}${cls}`;
3099
+ }
3091
3100
  function createPerformanceCapture() {
3092
3101
  const metrics = {};
3093
3102
  const observers = [];
@@ -3139,6 +3148,53 @@ function createPerformanceCapture() {
3139
3148
  observers.push(longTaskObserver);
3140
3149
  } catch {
3141
3150
  }
3151
+ try {
3152
+ const seenInteractions = /* @__PURE__ */ new Map();
3153
+ const inpObserver = new PerformanceObserver((list) => {
3154
+ for (const entry of list.getEntries()) {
3155
+ const interactionId = entry.interactionId;
3156
+ if (!interactionId) continue;
3157
+ const prev = seenInteractions.get(interactionId) ?? 0;
3158
+ if (entry.duration > prev) {
3159
+ seenInteractions.set(interactionId, entry.duration);
3160
+ }
3161
+ if (entry.duration > (metrics.inp ?? 0)) {
3162
+ metrics.inp = entry.duration;
3163
+ const inputDelay = entry.processingStart - entry.startTime;
3164
+ const processingDuration = entry.processingEnd - entry.processingStart;
3165
+ const presentationDelay = entry.startTime + entry.duration - entry.processingEnd;
3166
+ metrics.inpAttribution = {
3167
+ eventType: entry.name,
3168
+ targetSelector: describeElement(entry.target),
3169
+ inputDelay: Math.max(0, inputDelay),
3170
+ processingDuration: Math.max(0, processingDuration),
3171
+ presentationDelay: Math.max(0, presentationDelay)
3172
+ };
3173
+ }
3174
+ }
3175
+ });
3176
+ inpObserver.observe({
3177
+ type: "event",
3178
+ // `durationThreshold` filters out fast (< 40 ms) interactions
3179
+ // that sit below human perception. Spec-recommended floor.
3180
+ durationThreshold: INP_DURATION_THRESHOLD_MS,
3181
+ buffered: true
3182
+ });
3183
+ observers.push(inpObserver);
3184
+ } catch {
3185
+ }
3186
+ try {
3187
+ const fidObserver = new PerformanceObserver((list) => {
3188
+ for (const entry of list.getEntries()) {
3189
+ if (metrics.fid === void 0) {
3190
+ metrics.fid = entry.processingStart - entry.startTime;
3191
+ }
3192
+ }
3193
+ });
3194
+ fidObserver.observe({ type: "first-input", buffered: true });
3195
+ observers.push(fidObserver);
3196
+ } catch {
3197
+ }
3142
3198
  }
3143
3199
  if (typeof performance !== "undefined" && performance.getEntriesByType) {
3144
3200
  try {
@@ -3969,7 +4025,7 @@ function createProactiveManager(config = {}) {
3969
4025
 
3970
4026
  // src/version.ts
3971
4027
  var MUSHI_SDK_PACKAGE = "@mushi-mushi/web";
3972
- var MUSHI_SDK_VERSION = "1.3.0" ;
4028
+ var MUSHI_SDK_VERSION = "1.5.0" ;
3973
4029
 
3974
4030
  // src/mushi.ts
3975
4031
  var instance = null;
@@ -4446,17 +4502,43 @@ function createInstance(config) {
4446
4502
  };
4447
4503
  config.integrations.custom(builder);
4448
4504
  }
4449
- emit("report:submitted", { reportId: report.id });
4505
+ let finalReport = report;
4506
+ if (config.beforeSendFeedback) {
4507
+ try {
4508
+ const hookResult = await Promise.race([
4509
+ Promise.resolve(config.beforeSendFeedback(report)),
4510
+ // 2s timeout — async hooks must not block the user's "submit"
4511
+ // for longer than the network would. Falls back to original.
4512
+ new Promise(
4513
+ (resolve) => setTimeout(() => resolve(report), 2e3)
4514
+ )
4515
+ ]);
4516
+ if (hookResult === null) {
4517
+ log.info("Report dropped by beforeSendFeedback hook", { reportId: report.id });
4518
+ return;
4519
+ }
4520
+ finalReport = hookResult;
4521
+ } catch (err) {
4522
+ log.warn("beforeSendFeedback hook threw \u2014 sending unmodified report", {
4523
+ error: err instanceof Error ? err.message : String(err)
4524
+ });
4525
+ }
4526
+ }
4527
+ emit("report:submitted", { reportId: finalReport.id });
4450
4528
  if (typeof navigator !== "undefined" && !navigator.onLine) {
4451
- await offlineQueue.enqueue(report);
4452
- log.info("Offline \u2014 report queued", { reportId: report.id });
4453
- emit("report:queued", { reportId: report.id });
4529
+ await offlineQueue.enqueue(finalReport);
4530
+ log.info("Offline \u2014 report queued", { reportId: finalReport.id });
4531
+ emit("report:queued", { reportId: finalReport.id });
4454
4532
  return;
4455
4533
  }
4456
- const result = await apiClient2.submitReport(report);
4534
+ const result = await apiClient2.submitReport(finalReport);
4457
4535
  if (result.ok) {
4458
4536
  log.info("Report sent", { reportId: result.data?.reportId });
4459
4537
  emit("report:sent", { reportId: result.data?.reportId });
4538
+ if (result.data?.cursorAgentId) {
4539
+ const d = result.data;
4540
+ emit("report:dispatched", { reportId: d.reportId, agentId: d.cursorAgentId, fixId: d.fixId });
4541
+ }
4460
4542
  breadcrumbs.add({
4461
4543
  category: "lifecycle",
4462
4544
  level: "info",
@@ -4473,13 +4555,13 @@ function createInstance(config) {
4473
4555
  } catch {
4474
4556
  }
4475
4557
  } else {
4476
- log.warn("Report failed, queuing for retry", { reportId: report.id, error: result.error });
4477
- await offlineQueue.enqueue(report);
4478
- emit("report:failed", { reportId: report.id, error: result.error });
4558
+ log.warn("Report failed, queuing for retry", { reportId: finalReport.id, error: result.error });
4559
+ await offlineQueue.enqueue(finalReport);
4560
+ emit("report:failed", { reportId: finalReport.id, error: result.error });
4479
4561
  breadcrumbs.add({
4480
4562
  category: "lifecycle",
4481
4563
  level: "warning",
4482
- message: `Mushi report queued for retry (${report.id})`
4564
+ message: `Mushi report queued for retry (${finalReport.id})`
4483
4565
  });
4484
4566
  }
4485
4567
  pendingScreenshot = null;
@@ -4741,6 +4823,35 @@ function createInstance(config) {
4741
4823
  if (typeof globalThis !== "undefined" && (bootstrapConfig.debug ?? false)) {
4742
4824
  exposeMarketingRecorder(widget);
4743
4825
  }
4826
+ if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
4827
+ const SENTINEL_KEY = "mushi:last-run";
4828
+ let crashed = null;
4829
+ try {
4830
+ const previous = localStorage.getItem(SENTINEL_KEY);
4831
+ crashed = previous === null ? null : previous === "unfinished";
4832
+ localStorage.setItem(SENTINEL_KEY, "unfinished");
4833
+ } catch {
4834
+ crashed = null;
4835
+ }
4836
+ try {
4837
+ window.addEventListener("pagehide", () => {
4838
+ try {
4839
+ localStorage.setItem(SENTINEL_KEY, "clean");
4840
+ } catch {
4841
+ }
4842
+ });
4843
+ } catch {
4844
+ }
4845
+ if (typeof bootstrapConfig.onCrashedLastRun === "function") {
4846
+ try {
4847
+ bootstrapConfig.onCrashedLastRun({ crashed });
4848
+ } catch (err) {
4849
+ log.warn("onCrashedLastRun hook threw", {
4850
+ error: err instanceof Error ? err.message : String(err)
4851
+ });
4852
+ }
4853
+ }
4854
+ }
4744
4855
  return sdk;
4745
4856
  }
4746
4857
  function mergeRuntimeConfig(config, runtime) {