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