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