@mushi-mushi/web 1.8.0 → 1.10.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
@@ -551,23 +551,47 @@ function getWidgetStyles(theme) {
551
551
  font-weight: 600;
552
552
  color: ${ink};
553
553
  }
554
- .mushi-close, .mushi-back {
554
+ .mushi-close {
555
555
  background: none;
556
556
  border: none;
557
557
  cursor: pointer;
558
- padding: 4px;
558
+ padding: 2px 4px;
559
559
  color: ${inkMuted};
560
560
  font-family: ${fontBody};
561
- font-size: 14px;
561
+ font-size: 16px;
562
562
  line-height: 1;
563
- border-radius: 3px;
563
+ border-radius: 0;
564
564
  transition: color 150ms ${easeStamp};
565
565
  }
566
- .mushi-close:hover, .mushi-back:hover { color: ${widgetAccent}; }
567
- .mushi-close:focus-visible, .mushi-back:focus-visible {
566
+ .mushi-back {
567
+ align-self: flex-start;
568
+ background: none;
569
+ border: none;
570
+ cursor: pointer;
571
+ padding: 0;
572
+ margin: 0 0 2px;
573
+ color: ${inkMuted};
574
+ font-family: ${fontMono};
575
+ font-size: 10px;
576
+ font-weight: 500;
577
+ letter-spacing: 0.12em;
578
+ text-transform: uppercase;
579
+ line-height: 1.2;
580
+ border-radius: 0;
581
+ transition: color 150ms ${easeStamp};
582
+ }
583
+ .mushi-close:hover { color: ${widgetAccent}; }
584
+ .mushi-back:hover { color: ${ink}; }
585
+ .mushi-close:focus-visible {
568
586
  outline: 1.5px solid ${widgetAccent};
569
587
  outline-offset: 2px;
570
588
  }
589
+ .mushi-back:focus-visible {
590
+ outline: none;
591
+ color: ${widgetAccent};
592
+ text-decoration: underline;
593
+ text-underline-offset: 3px;
594
+ }
571
595
 
572
596
  /* \u2500\u2500 Body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
573
597
  Generous left/right padding (22px) so type breathes. Vertical
@@ -656,39 +680,121 @@ function getWidgetStyles(theme) {
656
680
  .mushi-report-row {
657
681
  width: 100%;
658
682
  display: grid;
659
- grid-template-columns: auto 1fr auto;
660
- gap: 8px;
683
+ grid-template-columns: 1fr auto;
684
+ gap: 10px;
661
685
  align-items: center;
662
- padding: 10px 0;
686
+ padding: 12px 4px 12px 0;
663
687
  border: 0;
664
688
  border-bottom: 1px solid ${rule};
665
689
  background: transparent;
666
690
  color: ${ink};
667
691
  cursor: pointer;
668
692
  text-align: left;
693
+ transition: background 180ms ${easeStamp}, padding-left 180ms ${easeStamp};
694
+ }
695
+ .mushi-report-row:hover,
696
+ .mushi-report-row:focus-visible {
697
+ background: ${isDark ? "rgba(242,235,221,0.04)" : "rgba(14,13,11,0.03)"};
698
+ padding-left: 4px;
699
+ }
700
+ .mushi-report-main {
701
+ min-width: 0;
702
+ display: grid;
703
+ gap: 6px;
704
+ }
705
+ .mushi-report-title {
706
+ font-size: 13px;
707
+ line-height: 1.35;
708
+ display: -webkit-box;
709
+ -webkit-line-clamp: 2;
710
+ -webkit-box-orient: vertical;
711
+ overflow: hidden;
712
+ }
713
+ .mushi-report-meta {
714
+ display: flex;
715
+ flex-wrap: wrap;
716
+ align-items: center;
717
+ gap: 8px;
669
718
  }
670
719
  .mushi-report-status {
720
+ display: inline-flex;
721
+ align-items: center;
671
722
  font-family: ${fontMono};
672
723
  font-size: 10px;
673
- color: ${widgetAccent};
724
+ font-weight: 600;
725
+ letter-spacing: 0.04em;
674
726
  text-transform: uppercase;
727
+ padding: 2px 7px;
728
+ border-radius: 999px;
729
+ border: 1px solid transparent;
730
+ }
731
+ .mushi-status-sent {
732
+ color: ${isDark ? "#A8C4FF" : "#1E4A8C"};
733
+ background: ${isDark ? "rgba(120,160,255,0.12)" : "rgba(30,74,140,0.08)"};
734
+ border-color: ${isDark ? "rgba(120,160,255,0.22)" : "rgba(30,74,140,0.16)"};
735
+ }
736
+ .mushi-status-review {
737
+ color: ${isDark ? "#FFD27A" : "#8A5A00"};
738
+ background: ${isDark ? "rgba(255,190,90,0.12)" : "rgba(180,120,0,0.10)"};
739
+ border-color: ${isDark ? "rgba(255,190,90,0.22)" : "rgba(180,120,0,0.18)"};
740
+ }
741
+ .mushi-status-fixing {
742
+ color: ${isDark ? "#FFB899" : "#9A3D12"};
743
+ background: ${isDark ? "rgba(255,120,60,0.12)" : "rgba(224,60,44,0.10)"};
744
+ border-color: ${isDark ? "rgba(255,120,60,0.24)" : "rgba(224,60,44,0.18)"};
745
+ }
746
+ .mushi-status-fixed {
747
+ color: ${isDark ? "#8FE3B0" : "#1F6B3A"};
748
+ background: ${isDark ? "rgba(80,200,130,0.12)" : "rgba(31,107,58,0.10)"};
749
+ border-color: ${isDark ? "rgba(80,200,130,0.22)" : "rgba(31,107,58,0.18)"};
750
+ }
751
+ .mushi-status-closed,
752
+ .mushi-status-unknown {
753
+ color: ${inkMuted};
754
+ background: ${isDark ? "rgba(242,235,221,0.06)" : "rgba(14,13,11,0.05)"};
755
+ border-color: ${ruleStrong};
675
756
  }
676
- .mushi-report-title {
677
- font-size: 13px;
678
- overflow: hidden;
679
- text-overflow: ellipsis;
680
- white-space: nowrap;
757
+ .mushi-report-when {
758
+ font-family: ${fontMono};
759
+ font-size: 10px;
760
+ color: ${inkFaint};
761
+ }
762
+ .mushi-unread-badge {
763
+ display: inline-flex;
764
+ align-items: center;
765
+ justify-content: center;
766
+ min-width: 18px;
767
+ height: 18px;
768
+ padding: 0 5px;
769
+ border-radius: 999px;
770
+ font-family: ${fontMono};
771
+ font-size: 10px;
772
+ font-weight: 700;
773
+ color: ${widgetAccentInk};
774
+ background: ${widgetAccent};
775
+ }
776
+ .mushi-report-chevron {
777
+ font-size: 18px;
778
+ line-height: 1;
779
+ color: ${inkFaint};
780
+ transition: color 180ms ${easeStamp}, transform 180ms ${easeStamp};
781
+ }
782
+ .mushi-report-row:hover .mushi-report-chevron,
783
+ .mushi-report-row:focus-visible .mushi-report-chevron {
784
+ color: ${widgetAccent};
785
+ transform: translateX(2px);
681
786
  }
682
787
  .mushi-thread-summary {
683
788
  border-bottom: 1px solid ${rule};
684
789
  padding-bottom: 10px;
685
790
  margin-bottom: 10px;
686
791
  }
687
- .mushi-thread-summary span {
688
- font-family: ${fontMono};
689
- font-size: 10px;
690
- color: ${widgetAccent};
691
- text-transform: uppercase;
792
+ .mushi-thread-summary-meta {
793
+ display: flex;
794
+ flex-wrap: wrap;
795
+ align-items: center;
796
+ gap: 10px;
797
+ margin-bottom: 8px;
692
798
  }
693
799
  .mushi-thread {
694
800
  display: grid;
@@ -1685,6 +1791,95 @@ var CATEGORY_ICONS = {
1685
1791
  other: "\u{1F4DD}"
1686
1792
  };
1687
1793
  var FEATURE_REQUEST_INTENT = "Feature request";
1794
+ function reporterStatusShort(status) {
1795
+ switch (status) {
1796
+ case "new":
1797
+ case "queued":
1798
+ case "pending":
1799
+ case "submitted":
1800
+ return "Sent";
1801
+ case "classified":
1802
+ case "triaged":
1803
+ case "grouped":
1804
+ case "dispatched":
1805
+ return "Review";
1806
+ case "fixing":
1807
+ return "Fixing";
1808
+ case "fixed":
1809
+ case "resolved":
1810
+ case "completed":
1811
+ return "Fixed";
1812
+ case "dismissed":
1813
+ return "Closed";
1814
+ default:
1815
+ return status.replace(/_/g, " ").slice(0, 12);
1816
+ }
1817
+ }
1818
+ function reporterStatusLabel(status) {
1819
+ switch (status) {
1820
+ case "new":
1821
+ case "queued":
1822
+ case "pending":
1823
+ case "submitted":
1824
+ return "Submitted";
1825
+ case "classified":
1826
+ case "triaged":
1827
+ case "grouped":
1828
+ case "dispatched":
1829
+ return "In review";
1830
+ case "fixing":
1831
+ return "Fix in progress";
1832
+ case "fixed":
1833
+ case "resolved":
1834
+ case "completed":
1835
+ return "Fixed";
1836
+ case "dismissed":
1837
+ return "Closed";
1838
+ default:
1839
+ return status.replace(/_/g, " ");
1840
+ }
1841
+ }
1842
+ function reporterStatusTone(status) {
1843
+ switch (status) {
1844
+ case "new":
1845
+ case "queued":
1846
+ case "pending":
1847
+ case "submitted":
1848
+ return "sent";
1849
+ case "classified":
1850
+ case "triaged":
1851
+ case "grouped":
1852
+ case "dispatched":
1853
+ return "review";
1854
+ case "fixing":
1855
+ return "fixing";
1856
+ case "fixed":
1857
+ case "resolved":
1858
+ case "completed":
1859
+ return "fixed";
1860
+ case "dismissed":
1861
+ return "closed";
1862
+ default:
1863
+ return "unknown";
1864
+ }
1865
+ }
1866
+ function formatRelativeTime(iso) {
1867
+ const then = Date.parse(iso);
1868
+ if (Number.isNaN(then)) return "";
1869
+ const diffMs = Date.now() - then;
1870
+ if (diffMs < 0) return "just now";
1871
+ const sec = Math.floor(diffMs / 1e3);
1872
+ if (sec < 60) return "just now";
1873
+ const min = Math.floor(sec / 60);
1874
+ if (min < 60) return `${min}m ago`;
1875
+ const hr = Math.floor(min / 60);
1876
+ if (hr < 24) return `${hr}h ago`;
1877
+ const day = Math.floor(hr / 24);
1878
+ if (day < 7) return `${day}d ago`;
1879
+ const week = Math.floor(day / 7);
1880
+ if (week < 5) return `${week}w ago`;
1881
+ return new Date(then).toLocaleDateString(void 0, { month: "short", day: "numeric" });
1882
+ }
1688
1883
  function pad2(n) {
1689
1884
  return n < 10 ? `0${n}` : String(n);
1690
1885
  }
@@ -1799,6 +1994,8 @@ var MushiWidget = class _MushiWidget {
1799
1994
  successTimer = null;
1800
1995
  autoCloseTimer = null;
1801
1996
  rewardsState = null;
1997
+ leaderboardEntries = null;
1998
+ leaderboardLoading = false;
1802
1999
  /** Server-confirmed id for the just-submitted report. Surfaces in
1803
2000
  * the success step as a copyable receipt + optional deep link to
1804
2001
  * the Mushi console (when `dashboardUrl` is configured). Cleared
@@ -2071,6 +2268,11 @@ var MushiWidget = class _MushiWidget {
2071
2268
  this.rewardsState = state;
2072
2269
  if (this.isOpen) this.render();
2073
2270
  }
2271
+ setLeaderboard(entries, loading = false) {
2272
+ this.leaderboardEntries = entries;
2273
+ this.leaderboardLoading = loading;
2274
+ if (this.isOpen && this.step === "leaderboard") this.render();
2275
+ }
2074
2276
  destroy() {
2075
2277
  if (this.successTimer !== null) {
2076
2278
  clearTimeout(this.successTimer);
@@ -2491,6 +2693,8 @@ var MushiWidget = class _MushiWidget {
2491
2693
  return this.renderReportsStep();
2492
2694
  case "report-detail":
2493
2695
  return this.renderReportDetailStep();
2696
+ case "leaderboard":
2697
+ return this.renderLeaderboardStep();
2494
2698
  }
2495
2699
  }
2496
2700
  renderOutdatedBanner() {
@@ -2523,7 +2727,7 @@ var MushiWidget = class _MushiWidget {
2523
2727
  renderHeader(opts) {
2524
2728
  const t = this.locale;
2525
2729
  const { title, showBack = false, step, eyebrow } = opts;
2526
- const eyebrowHtml = showBack ? `<button type="button" class="mushi-back" data-action="back" aria-label="${t.widget.back}">\u2190 ${t.widget.back}</button>` : `<span class="mushi-header-eyebrow">${eyebrow ?? "Mushi \xB7 Report"}</span>`;
2730
+ const eyebrowHtml = showBack ? `<button type="button" class="mushi-back" data-action="back" aria-label="${t.widget.back}">\u2190</button>` : `<span class="mushi-header-eyebrow">${eyebrow ?? "Mushi \xB7 Report"}</span>`;
2527
2731
  const counterHtml = step ? `<span class="mushi-step-counter" aria-label="Step ${step} of ${TOTAL_STEPS}"><b>${pad2(step)}</b> / ${pad2(TOTAL_STEPS)}</span>` : "";
2528
2732
  return `
2529
2733
  <div class="mushi-header">
@@ -2659,24 +2863,61 @@ var MushiWidget = class _MushiWidget {
2659
2863
  `;
2660
2864
  }
2661
2865
  renderReportsStep() {
2662
- const reports = this.reporterReports.map((report) => `
2663
- <button type="button" class="mushi-report-row" data-report-id="${escapeHtml(report.id)}">
2664
- <span class="mushi-report-status">${escapeHtml(report.status)}</span>
2665
- <span class="mushi-report-title">${escapeHtml(report.summary ?? report.description ?? `Report ${report.id.slice(0, 8)}`)}</span>
2666
- ${report.unread_count ? `<b>${report.unread_count}</b>` : ""}
2667
- </button>
2668
- `).join("");
2866
+ const reports = this.reporterReports.map((report) => {
2867
+ const title = report.summary ?? report.description ?? `Report ${report.id.slice(0, 8)}`;
2868
+ const tone = reporterStatusTone(report.status);
2869
+ const when = formatRelativeTime(report.created_at);
2870
+ const unread = report.unread_count && report.unread_count > 0 ? `<span class="mushi-unread-badge" aria-label="${report.unread_count} unread">${report.unread_count}</span>` : "";
2871
+ return `
2872
+ <button type="button" class="mushi-report-row" data-report-id="${escapeHtml(report.id)}" aria-label="View report: ${escapeHtml(title)}">
2873
+ <div class="mushi-report-main">
2874
+ <span class="mushi-report-title">${escapeHtml(title)}</span>
2875
+ <span class="mushi-report-meta">
2876
+ <span class="mushi-report-status mushi-status-${tone}">${escapeHtml(reporterStatusShort(report.status))}</span>
2877
+ ${when ? `<span class="mushi-report-when">${escapeHtml(when)}</span>` : ""}
2878
+ ${unread}
2879
+ </span>
2880
+ </div>
2881
+ <span class="mushi-report-chevron" aria-hidden="true">\u203A</span>
2882
+ </button>`;
2883
+ }).join("");
2884
+ const leaderboardBtn = this.rewardsState ? `<button type="button" class="mushi-leaderboard-link" data-action="open-leaderboard">\u{1F3C6} Leaderboard</button>` : "";
2669
2885
  return `
2670
2886
  ${this.renderHeader({ title: "Your reports", showBack: true, eyebrow: "Mushi \xB7 Inbox" })}
2671
2887
  <div class="mushi-body">
2672
2888
  ${this.reporterLoading ? '<p class="mushi-muted">Loading reports\u2026</p>' : ""}
2673
2889
  ${this.reporterError ? `<p class="mushi-error-inline">${escapeHtml(this.reporterError)}</p>` : ""}
2674
2890
  ${reports || (!this.reporterLoading ? '<p class="mushi-muted">No reports from this browser yet.</p>' : "")}
2891
+ ${leaderboardBtn}
2892
+ </div>
2893
+ `;
2894
+ }
2895
+ renderLeaderboardStep() {
2896
+ const myRank = this.rewardsState && this.leaderboardEntries ? this.leaderboardEntries.findIndex((e) => e.display_name === "You") + 1 : 0;
2897
+ const rows = (this.leaderboardEntries ?? []).map((e, i) => `
2898
+ <div class="mushi-lb-row ${i === 0 ? "mushi-lb-top" : ""}">
2899
+ <span class="mushi-lb-rank">#${i + 1}</span>
2900
+ <span class="mushi-lb-name">${escapeHtml(e.display_name)}</span>
2901
+ ${e.tier_name ? `<span class="mushi-lb-tier">${escapeHtml(e.tier_name)}</span>` : ""}
2902
+ <span class="mushi-lb-pts">${e.total_points.toLocaleString()} pts</span>
2903
+ </div>
2904
+ `).join("");
2905
+ return `
2906
+ ${this.renderHeader({ title: "\u{1F3C6} Leaderboard", showBack: true, eyebrow: "Mushi \xB7 Contributors" })}
2907
+ <div class="mushi-body">
2908
+ ${this.leaderboardLoading ? '<p class="mushi-muted">Loading leaderboard\u2026</p>' : ""}
2909
+ ${!this.leaderboardLoading && !this.leaderboardEntries?.length ? '<p class="mushi-muted">No contributors yet \u2014 be the first!</p>' : ""}
2910
+ <div class="mushi-lb-list">${rows}</div>
2911
+ ${myRank > 0 ? `<p class="mushi-lb-myrank">You are ranked #${myRank}</p>` : ""}
2912
+ <p class="mushi-lb-note">Top contributors this month \xB7 Points refresh monthly</p>
2675
2913
  </div>
2676
2914
  `;
2677
2915
  }
2678
2916
  renderReportDetailStep() {
2679
2917
  const report = this.reporterReports.find((r) => r.id === this.selectedReportId);
2918
+ const status = report?.status ?? "unknown";
2919
+ const tone = reporterStatusTone(status);
2920
+ const when = report?.created_at ? formatRelativeTime(report.created_at) : "";
2680
2921
  const comments = this.reporterComments.map((comment) => `
2681
2922
  <div class="mushi-thread-comment ${comment.author_kind}">
2682
2923
  <strong>${escapeHtml(comment.author_kind === "reporter" ? "You" : comment.author_name ?? "Developer")}</strong>
@@ -2687,7 +2928,10 @@ var MushiWidget = class _MushiWidget {
2687
2928
  ${this.renderHeader({ title: "Report thread", showBack: true, eyebrow: "Mushi \xB7 Inbox" })}
2688
2929
  <div class="mushi-body">
2689
2930
  <div class="mushi-thread-summary">
2690
- <span>${escapeHtml(report?.status ?? "unknown")}</span>
2931
+ <div class="mushi-thread-summary-meta">
2932
+ <span class="mushi-report-status mushi-status-${tone}">${escapeHtml(reporterStatusLabel(status))}</span>
2933
+ ${when ? `<span class="mushi-report-when">Reported ${escapeHtml(when)}</span>` : ""}
2934
+ </div>
2691
2935
  <p>${escapeHtml(report?.summary ?? report?.description ?? "Report details")}</p>
2692
2936
  </div>
2693
2937
  <div class="mushi-thread">
@@ -2995,6 +3239,8 @@ var MushiWidget = class _MushiWidget {
2995
3239
  } else if (this.step === "report-detail") {
2996
3240
  this.step = "reports";
2997
3241
  this.selectedReportId = null;
3242
+ } else if (this.step === "leaderboard") {
3243
+ this.step = "reports";
2998
3244
  }
2999
3245
  this.render();
3000
3246
  });
@@ -3014,6 +3260,11 @@ var MushiWidget = class _MushiWidget {
3014
3260
  if (reportId) void this.loadReporterComments(reportId);
3015
3261
  });
3016
3262
  });
3263
+ panel.querySelector('[data-action="open-leaderboard"]')?.addEventListener("click", () => {
3264
+ this.step = "leaderboard";
3265
+ this.callbacks.onLeaderboardOpen?.();
3266
+ this.render();
3267
+ });
3017
3268
  panel.querySelector('[data-action="reporter-reply"]')?.addEventListener("click", () => {
3018
3269
  void this.submitReporterReply(panel);
3019
3270
  });
@@ -3265,6 +3516,10 @@ var MushiWidget = class _MushiWidget {
3265
3516
  const submit = this.shadow.querySelector('[data-action="submit"]');
3266
3517
  submit?.click();
3267
3518
  }
3519
+ recorderOpenMyReports() {
3520
+ if (!this.isOpen) this.open();
3521
+ void this.loadReporterReports();
3522
+ }
3268
3523
  };
3269
3524
 
3270
3525
  // src/marketing-recorder.ts
@@ -3287,7 +3542,8 @@ function exposeMarketingRecorder(widget) {
3287
3542
  selectCategory: (category) => widget.recorderSelectCategory(category),
3288
3543
  selectIntent: (label) => widget.recorderSelectIntent(label),
3289
3544
  focusDescription: () => widget.recorderFocusDescription(),
3290
- submit: () => widget.recorderSubmit()
3545
+ submit: () => widget.recorderSubmit(),
3546
+ openMyReports: () => widget.recorderOpenMyReports()
3291
3547
  };
3292
3548
  globalThis.__mushiRecorder = api;
3293
3549
  }
@@ -3458,6 +3714,24 @@ async function fetchAndCacheTier(userId) {
3458
3714
  noteRewardsApiFailure(res.error?.code);
3459
3715
  return null;
3460
3716
  }
3717
+ async function fetchLeaderboard(limit = 10) {
3718
+ if (!apiClient || isRewardsApiBackedOff()) return null;
3719
+ try {
3720
+ const res = await apiClient.getHallOfFame(limit);
3721
+ if (res.ok && res.data) {
3722
+ return (res.data.data ?? []).map((e) => ({
3723
+ display_name: e.display_name,
3724
+ tier_name: e.tier_name,
3725
+ total_points: e.total_points,
3726
+ points_30d: e.points_30d
3727
+ }));
3728
+ }
3729
+ noteRewardsApiFailure(res.error?.code);
3730
+ return null;
3731
+ } catch {
3732
+ return null;
3733
+ }
3734
+ }
3461
3735
  var routeObserver = null;
3462
3736
  var clickHandler = null;
3463
3737
  var origPushState = null;
@@ -3727,6 +4001,29 @@ function readHeader(headers, name) {
3727
4001
 
3728
4002
  // src/capture/network.ts
3729
4003
  var MAX_ENTRIES2 = 30;
4004
+ var TRACEPARENT_VERSION = "00";
4005
+ function generateTraceparent() {
4006
+ const traceBytes = crypto.getRandomValues(new Uint8Array(16));
4007
+ const spanBytes = crypto.getRandomValues(new Uint8Array(8));
4008
+ const toHex = (bytes) => Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
4009
+ const traceId = toHex(traceBytes);
4010
+ const spanId = toHex(spanBytes);
4011
+ return {
4012
+ traceparent: `${TRACEPARENT_VERSION}-${traceId}-${spanId}-01`,
4013
+ traceId,
4014
+ spanId
4015
+ };
4016
+ }
4017
+ function matchesCorsUrls(url, corsUrls) {
4018
+ for (const pattern of corsUrls) {
4019
+ if (typeof pattern === "string") {
4020
+ if (url.includes(pattern)) return true;
4021
+ } else {
4022
+ if (pattern.test(url)) return true;
4023
+ }
4024
+ }
4025
+ return false;
4026
+ }
3730
4027
  function createNetworkCapture(options = {}) {
3731
4028
  const entries = [];
3732
4029
  const originalFetch = globalThis.fetch;
@@ -3737,15 +4034,29 @@ function createNetworkCapture(options = {}) {
3737
4034
  const url = getRequestUrl(input);
3738
4035
  const internalKind = getInternalRequestKind(input, init);
3739
4036
  const shouldRecord = !internalKind && !shouldIgnoreMushiUrl(url, activeOptions);
4037
+ let traceId;
4038
+ let patchedInit = init;
4039
+ const tp = activeOptions.tracePropagation;
4040
+ if (shouldRecord && tp?.enabled && tp.corsUrls?.length && matchesCorsUrls(url, tp.corsUrls)) {
4041
+ const { traceparent, traceId: tid } = generateTraceparent();
4042
+ traceId = tid;
4043
+ const existingHeaders = init?.headers ? new Headers(init.headers) : new Headers();
4044
+ existingHeaders.set("traceparent", traceparent);
4045
+ if (activeOptions.sessionId) {
4046
+ existingHeaders.set("x-mushi-session", activeOptions.sessionId);
4047
+ }
4048
+ patchedInit = { ...init, headers: existingHeaders };
4049
+ }
3740
4050
  try {
3741
- const response = await originalFetch.call(globalThis, input, init);
4051
+ const response = await originalFetch.call(globalThis, input, patchedInit);
3742
4052
  if (shouldRecord) {
3743
4053
  addEntry({
3744
4054
  method,
3745
4055
  url: truncateUrl(url),
3746
4056
  status: response.status,
3747
4057
  duration: Date.now() - startTime,
3748
- timestamp: startTime
4058
+ timestamp: startTime,
4059
+ ...traceId ? { traceId } : {}
3749
4060
  });
3750
4061
  }
3751
4062
  return response;
@@ -3757,7 +4068,8 @@ function createNetworkCapture(options = {}) {
3757
4068
  status: 0,
3758
4069
  duration: Date.now() - startTime,
3759
4070
  timestamp: startTime,
3760
- error: error instanceof Error ? error.message : "Network error"
4071
+ error: error instanceof Error ? error.message : "Network error",
4072
+ ...traceId ? { traceId } : {}
3761
4073
  });
3762
4074
  }
3763
4075
  throw error;
@@ -4990,7 +5302,7 @@ function createProactiveManager(config = {}) {
4990
5302
 
4991
5303
  // src/version.ts
4992
5304
  var MUSHI_SDK_PACKAGE = "@mushi-mushi/web";
4993
- var MUSHI_SDK_VERSION = "1.8.0" ;
5305
+ var MUSHI_SDK_VERSION = "1.10.0" ;
4994
5306
 
4995
5307
  // src/mushi.ts
4996
5308
  var instance = null;
@@ -5080,7 +5392,9 @@ function createInstance(config) {
5080
5392
  if (activeConfig.capture?.network !== false) {
5081
5393
  const networkOptions = {
5082
5394
  apiEndpoint: resolveApiEndpoint(activeConfig),
5083
- ignoreUrls: activeConfig.capture?.ignoreUrls
5395
+ ignoreUrls: activeConfig.capture?.ignoreUrls,
5396
+ tracePropagation: activeConfig.capture?.tracePropagation,
5397
+ sessionId: getSessionId()
5084
5398
  };
5085
5399
  if (networkCap) {
5086
5400
  networkCap.updateOptions(networkOptions);
@@ -5266,6 +5580,12 @@ function createInstance(config) {
5266
5580
  async onReporterReply(reportId, body) {
5267
5581
  const result = await apiClient2.replyToReporterReport(reportId, getReporterToken(), body);
5268
5582
  if (!result.ok) throw new Error(result.error?.message ?? "Could not send reply");
5583
+ },
5584
+ onLeaderboardOpen() {
5585
+ widget.setLeaderboard(null, true);
5586
+ void fetchLeaderboard(10).then((entries) => {
5587
+ widget.setLeaderboard(entries, false);
5588
+ });
5269
5589
  }
5270
5590
  }, MUSHI_SDK_VERSION);
5271
5591
  syncCaptureModules();
@@ -5522,7 +5842,7 @@ function createInstance(config) {
5522
5842
  await offlineQueue.enqueue(finalReport);
5523
5843
  log.info("Offline \u2014 report queued", { reportId: finalReport.id });
5524
5844
  emit("report:queued", { reportId: finalReport.id });
5525
- return;
5845
+ return { reportId: null, queuedOffline: true };
5526
5846
  }
5527
5847
  const result = await apiClient2.submitReport(finalReport);
5528
5848
  if (result.ok) {
@@ -5820,6 +6140,28 @@ function createInstance(config) {
5820
6140
  },
5821
6141
  pulseTrigger() {
5822
6142
  widget.pulseTrigger?.();
6143
+ },
6144
+ // ─── Reporter API (cross-platform) ────────────────────────────────
6145
+ async listMyReports() {
6146
+ const result = await apiClient2.listReporterReports(getReporterToken());
6147
+ if (!result.ok) return [];
6148
+ return result.data?.reports ?? [];
6149
+ },
6150
+ async listMyComments(reportId) {
6151
+ const result = await apiClient2.listReporterComments(reportId, getReporterToken());
6152
+ if (!result.ok) return [];
6153
+ return result.data?.comments ?? [];
6154
+ },
6155
+ async replyToReport(reportId, body) {
6156
+ const result = await apiClient2.replyToReporterReport(reportId, getReporterToken(), body);
6157
+ if (!result.ok) return null;
6158
+ return result.data?.comment ?? null;
6159
+ },
6160
+ async getHallOfFame(limit = 20) {
6161
+ const result = await apiClient2.getHallOfFame(limit);
6162
+ if (!result.ok) return [];
6163
+ const raw = result.data;
6164
+ return raw?.data ?? [];
5823
6165
  }
5824
6166
  };
5825
6167
  if (typeof globalThis !== "undefined" && (bootstrapConfig.debug ?? false)) {
@@ -6137,7 +6479,11 @@ function createNoopInstance() {
6137
6479
  recordActivity: () => {
6138
6480
  },
6139
6481
  pulseTrigger: () => {
6140
- }
6482
+ },
6483
+ listMyReports: async () => [],
6484
+ listMyComments: async () => [],
6485
+ replyToReport: async () => null,
6486
+ getHallOfFame: async () => []
6141
6487
  };
6142
6488
  }
6143
6489
  function installAutoBreadcrumbs(buffer) {