@mushi-mushi/web 1.2.1 → 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/CONTRIBUTING.md +11 -0
- package/dist/index.cjs +610 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +610 -93
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -47,10 +47,20 @@ var en = {
|
|
|
47
47
|
heading: "Tell us more",
|
|
48
48
|
descriptionPlaceholder: "Describe what happened\u2026",
|
|
49
49
|
screenshotButton: "Attach Screenshot",
|
|
50
|
-
screenshotAttached: "Screenshot attached",
|
|
50
|
+
screenshotAttached: "Screenshot attached \u2713",
|
|
51
|
+
screenshotCapturing: "Taking screenshot\u2026",
|
|
52
|
+
screenshotFailed: "Couldn't capture \u2014 describe it instead",
|
|
51
53
|
elementButton: "Select Element",
|
|
52
|
-
elementSelected: "Element selected",
|
|
53
|
-
|
|
54
|
+
elementSelected: "Element selected \u2713",
|
|
55
|
+
elementCapturing: "Click anything on the page\u2026",
|
|
56
|
+
elementSelectorHint: "Click any element \xB7 Esc to cancel",
|
|
57
|
+
optional: "(optional)",
|
|
58
|
+
tooShort: "A bit more detail helps us fix it faster",
|
|
59
|
+
examplePrompts: [
|
|
60
|
+
"The save button does nothing",
|
|
61
|
+
"Page froze for 10 seconds",
|
|
62
|
+
"Layout looks broken here"
|
|
63
|
+
]
|
|
54
64
|
}
|
|
55
65
|
};
|
|
56
66
|
|
|
@@ -97,10 +107,20 @@ var ja = {
|
|
|
97
107
|
heading: "\u8A73\u7D30\u3092\u6559\u3048\u3066\u304F\u3060\u3055\u3044",
|
|
98
108
|
descriptionPlaceholder: "\u4F55\u304C\u8D77\u304D\u305F\u304B\u8AAC\u660E\u3057\u3066\u304F\u3060\u3055\u3044\u2026",
|
|
99
109
|
screenshotButton: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u6DFB\u4ED8",
|
|
100
|
-
screenshotAttached: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u6DFB\u4ED8\u6E08\u307F",
|
|
110
|
+
screenshotAttached: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u6DFB\u4ED8\u6E08\u307F \u2713",
|
|
111
|
+
screenshotCapturing: "\u30B9\u30AF\u30EA\u30FC\u30F3\u30B7\u30E7\u30C3\u30C8\u64AE\u5F71\u4E2D\u2026",
|
|
112
|
+
screenshotFailed: "\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F \u2014 \u6587\u5B57\u3067\u6559\u3048\u3066\u304F\u3060\u3055\u3044",
|
|
101
113
|
elementButton: "\u8981\u7D20\u3092\u9078\u629E",
|
|
102
|
-
elementSelected: "\u8981\u7D20\u9078\u629E\u6E08\u307F",
|
|
103
|
-
|
|
114
|
+
elementSelected: "\u8981\u7D20\u9078\u629E\u6E08\u307F \u2713",
|
|
115
|
+
elementCapturing: "\u30DA\u30FC\u30B8\u4E0A\u306E\u8981\u7D20\u3092\u30AF\u30EA\u30C3\u30AF\u2026",
|
|
116
|
+
elementSelectorHint: "\u8981\u7D20\u3092\u30AF\u30EA\u30C3\u30AF \xB7 Esc \u3067\u30AD\u30E3\u30F3\u30BB\u30EB",
|
|
117
|
+
optional: "\uFF08\u4EFB\u610F\uFF09",
|
|
118
|
+
tooShort: "\u3082\u3046\u5C11\u3057\u8A73\u3057\u304F\u6559\u3048\u3066\u304F\u3060\u3055\u3044",
|
|
119
|
+
examplePrompts: [
|
|
120
|
+
"\u4FDD\u5B58\u30DC\u30BF\u30F3\u304C\u53CD\u5FDC\u3057\u306A\u3044",
|
|
121
|
+
"\u30DA\u30FC\u30B8\u304C10\u79D2\u56FA\u307E\u3063\u305F",
|
|
122
|
+
"\u30EC\u30A4\u30A2\u30A6\u30C8\u304C\u5D29\u308C\u3066\u3044\u308B"
|
|
123
|
+
]
|
|
104
124
|
}
|
|
105
125
|
};
|
|
106
126
|
|
|
@@ -147,10 +167,20 @@ var th = {
|
|
|
147
167
|
heading: "\u0E1A\u0E2D\u0E01\u0E40\u0E23\u0E32\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E40\u0E15\u0E34\u0E21",
|
|
148
168
|
descriptionPlaceholder: "\u0E2D\u0E18\u0E34\u0E1A\u0E32\u0E22\u0E2A\u0E34\u0E48\u0E07\u0E17\u0E35\u0E48\u0E40\u0E01\u0E34\u0E14\u0E02\u0E36\u0E49\u0E19\u2026",
|
|
149
169
|
screenshotButton: "\u0E41\u0E19\u0E1A\u0E2A\u0E01\u0E23\u0E35\u0E19\u0E0A\u0E47\u0E2D\u0E15",
|
|
150
|
-
screenshotAttached: "\u0E41\u0E19\u0E1A\u0E2A\u0E01\u0E23\u0E35\u0E19\u0E0A\u0E47\u0E2D\u0E15\u0E41\u0E25\u0E49\u0E27",
|
|
170
|
+
screenshotAttached: "\u0E41\u0E19\u0E1A\u0E2A\u0E01\u0E23\u0E35\u0E19\u0E0A\u0E47\u0E2D\u0E15\u0E41\u0E25\u0E49\u0E27 \u2713",
|
|
171
|
+
screenshotCapturing: "\u0E01\u0E33\u0E25\u0E31\u0E07\u0E16\u0E48\u0E32\u0E22\u0E2A\u0E01\u0E23\u0E35\u0E19\u0E0A\u0E47\u0E2D\u0E15\u2026",
|
|
172
|
+
screenshotFailed: "\u0E44\u0E21\u0E48\u0E2A\u0E32\u0E21\u0E32\u0E23\u0E16\u0E16\u0E48\u0E32\u0E22\u0E20\u0E32\u0E1E\u0E44\u0E14\u0E49 \u2014 \u0E42\u0E1B\u0E23\u0E14\u0E2D\u0E18\u0E34\u0E1A\u0E32\u0E22\u0E41\u0E17\u0E19",
|
|
151
173
|
elementButton: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A",
|
|
152
|
-
elementSelected: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E41\u0E25\u0E49\u0E27",
|
|
153
|
-
|
|
174
|
+
elementSelected: "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E41\u0E25\u0E49\u0E27 \u2713",
|
|
175
|
+
elementCapturing: "\u0E04\u0E25\u0E34\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E1A\u0E19\u0E2B\u0E19\u0E49\u0E32\u2026",
|
|
176
|
+
elementSelectorHint: "\u0E04\u0E25\u0E34\u0E01\u0E2D\u0E07\u0E04\u0E4C\u0E1B\u0E23\u0E30\u0E01\u0E2D\u0E1A\u0E43\u0E14\u0E01\u0E47\u0E44\u0E14\u0E49 \xB7 Esc \u0E40\u0E1E\u0E37\u0E48\u0E2D\u0E22\u0E01\u0E40\u0E25\u0E34\u0E01",
|
|
177
|
+
optional: "(\u0E44\u0E21\u0E48\u0E1A\u0E31\u0E07\u0E04\u0E31\u0E1A)",
|
|
178
|
+
tooShort: "\u0E01\u0E23\u0E38\u0E13\u0E32\u0E43\u0E2B\u0E49\u0E23\u0E32\u0E22\u0E25\u0E30\u0E40\u0E2D\u0E35\u0E22\u0E14\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E40\u0E15\u0E34\u0E21",
|
|
179
|
+
examplePrompts: [
|
|
180
|
+
"\u0E1B\u0E38\u0E48\u0E21\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E44\u0E21\u0E48\u0E17\u0E33\u0E07\u0E32\u0E19",
|
|
181
|
+
"\u0E2B\u0E19\u0E49\u0E32\u0E04\u0E49\u0E32\u0E07\u0E19\u0E32\u0E19 10 \u0E27\u0E34\u0E19\u0E32\u0E17\u0E35",
|
|
182
|
+
"\u0E40\u0E25\u0E22\u0E4C\u0E40\u0E2D\u0E32\u0E15\u0E4C\u0E1E\u0E31\u0E07"
|
|
183
|
+
]
|
|
154
184
|
}
|
|
155
185
|
};
|
|
156
186
|
|
|
@@ -197,18 +227,29 @@ var es = {
|
|
|
197
227
|
heading: "Cu\xE9ntanos m\xE1s",
|
|
198
228
|
descriptionPlaceholder: "Describe lo que pas\xF3\u2026",
|
|
199
229
|
screenshotButton: "Adjuntar captura",
|
|
200
|
-
screenshotAttached: "Captura adjunta",
|
|
230
|
+
screenshotAttached: "Captura adjunta \u2713",
|
|
231
|
+
screenshotCapturing: "Tomando captura\u2026",
|
|
232
|
+
screenshotFailed: "No se pudo capturar \u2014 descr\xEDbelo en su lugar",
|
|
201
233
|
elementButton: "Seleccionar elemento",
|
|
202
|
-
elementSelected: "Elemento seleccionado",
|
|
203
|
-
|
|
234
|
+
elementSelected: "Elemento seleccionado \u2713",
|
|
235
|
+
elementCapturing: "Haz clic en cualquier elemento\u2026",
|
|
236
|
+
elementSelectorHint: "Clic en cualquier elemento \xB7 Esc para cancelar",
|
|
237
|
+
optional: "(opcional)",
|
|
238
|
+
tooShort: "Un poco m\xE1s de detalle nos ayuda a resolverlo",
|
|
239
|
+
examplePrompts: [
|
|
240
|
+
"El bot\xF3n guardar no responde",
|
|
241
|
+
"La p\xE1gina se congel\xF3 10 segundos",
|
|
242
|
+
"El dise\xF1o se ve roto aqu\xED"
|
|
243
|
+
]
|
|
204
244
|
}
|
|
205
245
|
};
|
|
206
246
|
|
|
207
247
|
// src/i18n/index.ts
|
|
208
248
|
var locales = { en, ja, th, es };
|
|
209
249
|
function getLocale(code) {
|
|
210
|
-
|
|
211
|
-
|
|
250
|
+
const resolved = code && code !== "auto" ? code : typeof navigator !== "undefined" ? navigator.language ?? navigator.languages?.[0] : void 0;
|
|
251
|
+
if (!resolved) return en;
|
|
252
|
+
const base = resolved.split("-")[0].toLowerCase();
|
|
212
253
|
return locales[base] ?? en;
|
|
213
254
|
}
|
|
214
255
|
function getAvailableLocales() {
|
|
@@ -241,6 +282,7 @@ function getWidgetStyles(theme) {
|
|
|
241
282
|
-webkit-font-smoothing: antialiased;
|
|
242
283
|
-moz-osx-font-smoothing: grayscale;
|
|
243
284
|
font-feature-settings: 'ss01', 'cv11'; /* nicer system-ui glyphs where supported */
|
|
285
|
+
--mushi-ok: ${isDark ? "#4ade80" : "#16a34a"};
|
|
244
286
|
}
|
|
245
287
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
246
288
|
button { font-family: inherit; }
|
|
@@ -693,6 +735,51 @@ function getWidgetStyles(theme) {
|
|
|
693
735
|
box-shadow: inset 2px 0 0 ${vermillion};
|
|
694
736
|
}
|
|
695
737
|
|
|
738
|
+
/* Example starter chips \u2014 reduce first-report activation energy */
|
|
739
|
+
.mushi-example-chips {
|
|
740
|
+
display: flex;
|
|
741
|
+
flex-wrap: wrap;
|
|
742
|
+
gap: 6px;
|
|
743
|
+
margin-bottom: 10px;
|
|
744
|
+
}
|
|
745
|
+
.mushi-example-chip {
|
|
746
|
+
padding: 4px 10px;
|
|
747
|
+
border: 1px solid ${rule};
|
|
748
|
+
border-radius: 12px;
|
|
749
|
+
background: transparent;
|
|
750
|
+
color: ${inkMuted};
|
|
751
|
+
font-family: ${fontBody};
|
|
752
|
+
font-size: 11px;
|
|
753
|
+
cursor: pointer;
|
|
754
|
+
transition: color 150ms ${easeStamp}, border-color 150ms ${easeStamp}, background 150ms ${easeStamp};
|
|
755
|
+
white-space: nowrap;
|
|
756
|
+
}
|
|
757
|
+
.mushi-example-chip:hover {
|
|
758
|
+
color: ${ink};
|
|
759
|
+
border-color: ${inkMuted};
|
|
760
|
+
background: ${isDark ? "rgba(242,235,221,0.06)" : "rgba(14,13,11,0.04)"};
|
|
761
|
+
}
|
|
762
|
+
.mushi-example-chip:focus-visible {
|
|
763
|
+
outline: 2px solid ${vermillion};
|
|
764
|
+
outline-offset: 2px;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/* Textarea wrapper to position char counter */
|
|
768
|
+
.mushi-textarea-wrap {
|
|
769
|
+
position: relative;
|
|
770
|
+
}
|
|
771
|
+
.mushi-char-counter {
|
|
772
|
+
position: absolute;
|
|
773
|
+
bottom: 4px;
|
|
774
|
+
right: 0;
|
|
775
|
+
font-family: ${fontMono};
|
|
776
|
+
font-size: 10px;
|
|
777
|
+
letter-spacing: 0.04em;
|
|
778
|
+
color: ${inkFaint};
|
|
779
|
+
pointer-events: none;
|
|
780
|
+
transition: color 200ms ${easeStamp};
|
|
781
|
+
}
|
|
782
|
+
|
|
696
783
|
.mushi-textarea {
|
|
697
784
|
width: 100%;
|
|
698
785
|
min-height: 96px;
|
|
@@ -755,10 +842,34 @@ function getWidgetStyles(theme) {
|
|
|
755
842
|
border-color: ${vermillion};
|
|
756
843
|
background: ${vermillionWash};
|
|
757
844
|
}
|
|
845
|
+
.mushi-attach-btn.loading {
|
|
846
|
+
opacity: 0.7;
|
|
847
|
+
cursor: wait;
|
|
848
|
+
}
|
|
849
|
+
.mushi-attach-btn.error {
|
|
850
|
+
color: ${vermillion};
|
|
851
|
+
border-color: ${vermillionWash};
|
|
852
|
+
}
|
|
758
853
|
.mushi-attach-btn:focus-visible {
|
|
759
854
|
outline: 2px solid ${vermillion};
|
|
760
855
|
outline-offset: 2px;
|
|
761
856
|
}
|
|
857
|
+
@keyframes mushi-spin {
|
|
858
|
+
to { transform: rotate(360deg); }
|
|
859
|
+
}
|
|
860
|
+
@keyframes mushi-fade-in {
|
|
861
|
+
from { opacity: 0; transform: translateY(4px); }
|
|
862
|
+
to { opacity: 1; transform: translateY(0); }
|
|
863
|
+
}
|
|
864
|
+
.mushi-spinner {
|
|
865
|
+
display: inline-block;
|
|
866
|
+
width: 10px;
|
|
867
|
+
height: 10px;
|
|
868
|
+
border: 1.5px solid currentColor;
|
|
869
|
+
border-top-color: transparent;
|
|
870
|
+
border-radius: 50%;
|
|
871
|
+
animation: mushi-spin 0.7s linear infinite;
|
|
872
|
+
}
|
|
762
873
|
|
|
763
874
|
.mushi-footer {
|
|
764
875
|
padding: 14px 22px 16px;
|
|
@@ -1232,7 +1343,8 @@ var MushiWidget = class {
|
|
|
1232
1343
|
draggable: config.draggable ?? false,
|
|
1233
1344
|
brandFooter: config.brandFooter ?? true,
|
|
1234
1345
|
outdatedBanner: config.outdatedBanner ?? "auto",
|
|
1235
|
-
betaMode: config.betaMode ?? {}
|
|
1346
|
+
betaMode: config.betaMode ?? {},
|
|
1347
|
+
minDescriptionLength: config.minDescriptionLength ?? 20
|
|
1236
1348
|
};
|
|
1237
1349
|
this.callbacks = callbacks;
|
|
1238
1350
|
this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
|
|
@@ -1251,12 +1363,22 @@ var MushiWidget = class {
|
|
|
1251
1363
|
selectedCategory = null;
|
|
1252
1364
|
selectedIntent = null;
|
|
1253
1365
|
screenshotAttached = false;
|
|
1366
|
+
screenshotCapturing = false;
|
|
1367
|
+
screenshotError = false;
|
|
1254
1368
|
allowScreenshotRemove = true;
|
|
1255
1369
|
elementSelected = false;
|
|
1370
|
+
elementCapturing = false;
|
|
1256
1371
|
submitting = false;
|
|
1372
|
+
/** Hint element injected outside the shadow DOM during element selection. */
|
|
1373
|
+
selectorHint = null;
|
|
1257
1374
|
triggerVisible = true;
|
|
1258
1375
|
triggerShrunk = false;
|
|
1259
1376
|
triggerHiddenByScroll = false;
|
|
1377
|
+
/** Milliseconds since mount — used for the 30s first-time nudge gate. */
|
|
1378
|
+
mountedAt = null;
|
|
1379
|
+
nudgeShown = false;
|
|
1380
|
+
nudgeEl = null;
|
|
1381
|
+
nudgeTimer = null;
|
|
1260
1382
|
sdkFreshness = null;
|
|
1261
1383
|
reporterReports = [];
|
|
1262
1384
|
reporterComments = [];
|
|
@@ -1283,6 +1405,7 @@ var MushiWidget = class {
|
|
|
1283
1405
|
this.syncAttachedLaunchers();
|
|
1284
1406
|
this.syncSmartHide();
|
|
1285
1407
|
this.render();
|
|
1408
|
+
this.mountedAt = Date.now();
|
|
1286
1409
|
}
|
|
1287
1410
|
getIsMounted() {
|
|
1288
1411
|
return this.host.isConnected;
|
|
@@ -1309,7 +1432,8 @@ var MushiWidget = class {
|
|
|
1309
1432
|
...config.draggable !== void 0 ? { draggable: config.draggable } : {},
|
|
1310
1433
|
...config.brandFooter !== void 0 ? { brandFooter: config.brandFooter } : {},
|
|
1311
1434
|
...config.outdatedBanner !== void 0 ? { outdatedBanner: config.outdatedBanner } : {},
|
|
1312
|
-
...config.betaMode !== void 0 ? { betaMode: config.betaMode } : {}
|
|
1435
|
+
...config.betaMode !== void 0 ? { betaMode: config.betaMode } : {},
|
|
1436
|
+
...config.minDescriptionLength !== void 0 ? { minDescriptionLength: config.minDescriptionLength } : {}
|
|
1313
1437
|
};
|
|
1314
1438
|
this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
|
|
1315
1439
|
this.syncAttachedLaunchers();
|
|
@@ -1320,9 +1444,13 @@ var MushiWidget = class {
|
|
|
1320
1444
|
if (this.isOpen) return;
|
|
1321
1445
|
this.isOpen = true;
|
|
1322
1446
|
this.screenshotAttached = false;
|
|
1447
|
+
this.screenshotCapturing = false;
|
|
1448
|
+
this.screenshotError = false;
|
|
1323
1449
|
this.elementSelected = false;
|
|
1450
|
+
this.elementCapturing = false;
|
|
1324
1451
|
this.submitting = false;
|
|
1325
1452
|
this.submittedAt = null;
|
|
1453
|
+
this.removeSelectorHint();
|
|
1326
1454
|
if (options?.category) {
|
|
1327
1455
|
this.selectedCategory = options.category;
|
|
1328
1456
|
this.selectedIntent = null;
|
|
@@ -1378,8 +1506,114 @@ var MushiWidget = class {
|
|
|
1378
1506
|
}
|
|
1379
1507
|
setElementSelected(selected) {
|
|
1380
1508
|
this.elementSelected = selected;
|
|
1509
|
+
this.elementCapturing = false;
|
|
1510
|
+
this.removeSelectorHint();
|
|
1381
1511
|
if (this.isOpen) this.render();
|
|
1382
1512
|
}
|
|
1513
|
+
setScreenshotCapturing(capturing) {
|
|
1514
|
+
this.screenshotCapturing = capturing;
|
|
1515
|
+
this.screenshotError = false;
|
|
1516
|
+
if (this.isOpen) this.render();
|
|
1517
|
+
}
|
|
1518
|
+
setScreenshotError(failed) {
|
|
1519
|
+
this.screenshotError = failed;
|
|
1520
|
+
this.screenshotCapturing = false;
|
|
1521
|
+
if (this.isOpen) this.render();
|
|
1522
|
+
}
|
|
1523
|
+
setElementCapturing(capturing) {
|
|
1524
|
+
this.elementCapturing = capturing;
|
|
1525
|
+
if (capturing) {
|
|
1526
|
+
this.showSelectorHint();
|
|
1527
|
+
} else {
|
|
1528
|
+
this.removeSelectorHint();
|
|
1529
|
+
}
|
|
1530
|
+
if (this.isOpen) this.render();
|
|
1531
|
+
}
|
|
1532
|
+
/** Hide the widget panel (but keep the host element) during element selection
|
|
1533
|
+
* so the user can click any element on the page without the panel
|
|
1534
|
+
* intercepting the event. */
|
|
1535
|
+
hidePanel() {
|
|
1536
|
+
const panel = this.shadow.querySelector(".mushi-panel");
|
|
1537
|
+
if (panel) panel.style.display = "none";
|
|
1538
|
+
}
|
|
1539
|
+
showPanel() {
|
|
1540
|
+
const panel = this.shadow.querySelector(".mushi-panel");
|
|
1541
|
+
if (panel) panel.style.display = "";
|
|
1542
|
+
}
|
|
1543
|
+
showSelectorHint() {
|
|
1544
|
+
this.removeSelectorHint();
|
|
1545
|
+
const hint = document.createElement("div");
|
|
1546
|
+
hint.id = "mushi-selector-hint";
|
|
1547
|
+
hint.setAttribute("role", "status");
|
|
1548
|
+
hint.setAttribute("aria-live", "polite");
|
|
1549
|
+
hint.style.cssText = `
|
|
1550
|
+
position: fixed;
|
|
1551
|
+
bottom: 24px;
|
|
1552
|
+
left: 50%;
|
|
1553
|
+
transform: translateX(-50%);
|
|
1554
|
+
z-index: 2147483646;
|
|
1555
|
+
background: rgba(17,17,17,0.92);
|
|
1556
|
+
color: #fff;
|
|
1557
|
+
font-family: ui-monospace, SFMono-Regular, monospace;
|
|
1558
|
+
font-size: 12px;
|
|
1559
|
+
letter-spacing: 0.04em;
|
|
1560
|
+
padding: 8px 16px;
|
|
1561
|
+
border-radius: 20px;
|
|
1562
|
+
pointer-events: none;
|
|
1563
|
+
white-space: nowrap;
|
|
1564
|
+
backdrop-filter: blur(4px);
|
|
1565
|
+
box-shadow: 0 2px 12px rgba(0,0,0,0.35);
|
|
1566
|
+
`;
|
|
1567
|
+
hint.textContent = this.locale.step3.elementSelectorHint;
|
|
1568
|
+
document.body.appendChild(hint);
|
|
1569
|
+
this.selectorHint = hint;
|
|
1570
|
+
}
|
|
1571
|
+
removeSelectorHint() {
|
|
1572
|
+
this.selectorHint?.remove();
|
|
1573
|
+
this.selectorHint = null;
|
|
1574
|
+
document.getElementById("mushi-selector-hint")?.remove();
|
|
1575
|
+
}
|
|
1576
|
+
showNudge() {
|
|
1577
|
+
if (this.nudgeShown || this.nudgeEl) return;
|
|
1578
|
+
this.nudgeShown = true;
|
|
1579
|
+
const trigger = this.shadow.querySelector(".mushi-trigger");
|
|
1580
|
+
const rect = trigger?.getBoundingClientRect();
|
|
1581
|
+
const nudge = document.createElement("div");
|
|
1582
|
+
nudge.id = "mushi-nudge-bubble";
|
|
1583
|
+
nudge.setAttribute("role", "tooltip");
|
|
1584
|
+
const isRight = this.config.position.includes("right");
|
|
1585
|
+
nudge.style.cssText = `
|
|
1586
|
+
position: fixed;
|
|
1587
|
+
z-index: 2147483645;
|
|
1588
|
+
${rect ? `bottom: ${window.innerHeight - rect.top + 8}px; ${isRight ? `right: ${window.innerWidth - rect.right}px;` : `left: ${rect.left}px;`}` : "bottom: 80px; right: 24px;"}
|
|
1589
|
+
background: rgba(17,17,17,0.92);
|
|
1590
|
+
color: #fff;
|
|
1591
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
1592
|
+
font-size: 12px;
|
|
1593
|
+
line-height: 1.4;
|
|
1594
|
+
padding: 8px 12px;
|
|
1595
|
+
border-radius: 8px;
|
|
1596
|
+
max-width: 200px;
|
|
1597
|
+
pointer-events: none;
|
|
1598
|
+
backdrop-filter: blur(4px);
|
|
1599
|
+
box-shadow: 0 2px 12px rgba(0,0,0,0.35);
|
|
1600
|
+
animation: mushi-fade-in 0.15s ease forwards;
|
|
1601
|
+
`;
|
|
1602
|
+
nudge.textContent = this.locale.step3.tooShort.startsWith("A bit") ? "Found a bug? One sentence is enough \u{1F41B}" : "\u30D0\u30B0\u3092\u898B\u3064\u3051\u305F\uFF1F\u4E00\u884C\u3067\u5927\u4E08\u592B\u3067\u3059 \u{1F41B}";
|
|
1603
|
+
document.body.appendChild(nudge);
|
|
1604
|
+
this.nudgeEl = nudge;
|
|
1605
|
+
if (this.nudgeTimer !== null) clearTimeout(this.nudgeTimer);
|
|
1606
|
+
this.nudgeTimer = setTimeout(() => this.removeNudge(), 5e3);
|
|
1607
|
+
}
|
|
1608
|
+
removeNudge() {
|
|
1609
|
+
if (this.nudgeTimer !== null) {
|
|
1610
|
+
clearTimeout(this.nudgeTimer);
|
|
1611
|
+
this.nudgeTimer = null;
|
|
1612
|
+
}
|
|
1613
|
+
this.nudgeEl?.remove();
|
|
1614
|
+
this.nudgeEl = null;
|
|
1615
|
+
document.getElementById("mushi-nudge-bubble")?.remove();
|
|
1616
|
+
}
|
|
1383
1617
|
setSdkFreshness(info) {
|
|
1384
1618
|
this.sdkFreshness = info;
|
|
1385
1619
|
if (this.isOpen) this.render();
|
|
@@ -1405,6 +1639,8 @@ var MushiWidget = class {
|
|
|
1405
1639
|
this.smartHideCleanup = null;
|
|
1406
1640
|
this.attachedLaunchers.forEach((cleanup) => cleanup());
|
|
1407
1641
|
this.attachedLaunchers = [];
|
|
1642
|
+
this.removeSelectorHint();
|
|
1643
|
+
this.removeNudge();
|
|
1408
1644
|
this.host.remove();
|
|
1409
1645
|
}
|
|
1410
1646
|
syncAttachedLaunchers() {
|
|
@@ -1500,9 +1736,22 @@ var MushiWidget = class {
|
|
|
1500
1736
|
trigger.style.zIndex = String(this.config.zIndex);
|
|
1501
1737
|
this.applyInsetVars(trigger);
|
|
1502
1738
|
trigger.addEventListener("click", () => {
|
|
1739
|
+
this.removeNudge();
|
|
1503
1740
|
if (this.isOpen) this.close();
|
|
1504
1741
|
else this.open();
|
|
1505
1742
|
});
|
|
1743
|
+
trigger.addEventListener("mouseenter", () => {
|
|
1744
|
+
const onPageMs = this.mountedAt ? Date.now() - this.mountedAt : 0;
|
|
1745
|
+
if (!this.nudgeShown && !this.isOpen && onPageMs >= 3e4) {
|
|
1746
|
+
this.showNudge();
|
|
1747
|
+
}
|
|
1748
|
+
});
|
|
1749
|
+
trigger.addEventListener("mouseleave", () => {
|
|
1750
|
+
if (this.nudgeEl) {
|
|
1751
|
+
if (this.nudgeTimer !== null) clearTimeout(this.nudgeTimer);
|
|
1752
|
+
this.nudgeTimer = setTimeout(() => this.removeNudge(), 2e3);
|
|
1753
|
+
}
|
|
1754
|
+
});
|
|
1506
1755
|
this.shadow.appendChild(trigger);
|
|
1507
1756
|
}
|
|
1508
1757
|
const panel = document.createElement("div");
|
|
@@ -1754,25 +2003,62 @@ var MushiWidget = class {
|
|
|
1754
2003
|
${this.renderStepIndicator(STEP_NUMBER.intent)}
|
|
1755
2004
|
`;
|
|
1756
2005
|
}
|
|
2006
|
+
effectiveMinLength() {
|
|
2007
|
+
const base = this.config.minDescriptionLength ?? 20;
|
|
2008
|
+
const lang = this.config.locale === "auto" ? typeof navigator !== "undefined" ? navigator.language ?? "" : "" : this.config.locale ?? "";
|
|
2009
|
+
const isCjk = /^(ja|zh|ko)/i.test(lang);
|
|
2010
|
+
return isCjk ? Math.max(4, Math.floor(base / 2)) : base;
|
|
2011
|
+
}
|
|
1757
2012
|
renderDetailsStep() {
|
|
1758
2013
|
const t = this.locale;
|
|
2014
|
+
const minLen = this.effectiveMinLength();
|
|
2015
|
+
const screenshotLabel = this.screenshotCapturing ? t.step3.screenshotCapturing : this.screenshotError ? t.step3.screenshotFailed : this.screenshotAttached ? t.step3.screenshotAttached : t.step3.screenshotButton;
|
|
2016
|
+
const screenshotClass = [
|
|
2017
|
+
"mushi-attach-btn",
|
|
2018
|
+
this.screenshotAttached ? "active" : "",
|
|
2019
|
+
this.screenshotError ? "error" : "",
|
|
2020
|
+
this.screenshotCapturing ? "loading" : ""
|
|
2021
|
+
].filter(Boolean).join(" ");
|
|
2022
|
+
const elementLabel = this.elementCapturing ? t.step3.elementCapturing : this.elementSelected ? t.step3.elementSelected : t.step3.elementButton;
|
|
2023
|
+
const elementClass = [
|
|
2024
|
+
"mushi-attach-btn",
|
|
2025
|
+
this.elementSelected ? "active" : "",
|
|
2026
|
+
this.elementCapturing ? "loading" : ""
|
|
2027
|
+
].filter(Boolean).join(" ");
|
|
2028
|
+
const exampleChips = t.step3.examplePrompts.map((p) => `<button type="button" class="mushi-example-chip" data-example="${escapeHtml(p)}">${escapeHtml(p)}</button>`).join("");
|
|
1759
2029
|
return `
|
|
1760
2030
|
${this.renderHeader({ title: t.step3.heading, showBack: true, step: STEP_NUMBER.details })}
|
|
1761
2031
|
<div class="mushi-body">
|
|
1762
|
-
<
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
2032
|
+
<div class="mushi-example-chips" aria-label="Example prompts">${exampleChips}</div>
|
|
2033
|
+
<div class="mushi-textarea-wrap">
|
|
2034
|
+
<textarea
|
|
2035
|
+
class="mushi-textarea"
|
|
2036
|
+
placeholder="${t.step3.descriptionPlaceholder}"
|
|
2037
|
+
rows="4"
|
|
2038
|
+
aria-label="${t.step3.heading}"
|
|
2039
|
+
autofocus
|
|
2040
|
+
></textarea>
|
|
2041
|
+
<div class="mushi-char-counter" data-role="char-counter" aria-hidden="true">
|
|
2042
|
+
<span data-role="char-current">0</span>/<span data-role="char-min">${minLen}</span>
|
|
2043
|
+
</div>
|
|
2044
|
+
</div>
|
|
1769
2045
|
<div class="mushi-attachments">
|
|
1770
|
-
<button type="button" class="
|
|
1771
|
-
|
|
2046
|
+
<button type="button" class="${screenshotClass}"
|
|
2047
|
+
data-action="screenshot"
|
|
2048
|
+
${this.screenshotCapturing ? "disabled" : ""}
|
|
2049
|
+
aria-label="${escapeHtml(screenshotLabel)}"
|
|
2050
|
+
>
|
|
2051
|
+
${this.screenshotCapturing ? '<span class="mushi-spinner" aria-hidden="true"></span>' : "\u{1F4F8}"}
|
|
2052
|
+
${escapeHtml(screenshotLabel)}
|
|
1772
2053
|
</button>
|
|
1773
|
-
${this.screenshotAttached && this.allowScreenshotRemove ? '<button type="button" class="mushi-attach-btn danger" data-action="remove-screenshot">\u2715 Remove
|
|
1774
|
-
<button type="button" class="
|
|
1775
|
-
|
|
2054
|
+
${this.screenshotAttached && this.allowScreenshotRemove ? '<button type="button" class="mushi-attach-btn danger" data-action="remove-screenshot" aria-label="Remove screenshot">\u2715 Remove</button>' : ""}
|
|
2055
|
+
<button type="button" class="${elementClass}"
|
|
2056
|
+
data-action="element"
|
|
2057
|
+
${this.elementCapturing ? "disabled" : ""}
|
|
2058
|
+
aria-label="${escapeHtml(elementLabel)}"
|
|
2059
|
+
>
|
|
2060
|
+
${this.elementCapturing ? '<span class="mushi-spinner" aria-hidden="true"></span>' : "\u{1F3AF}"}
|
|
2061
|
+
${escapeHtml(elementLabel)}
|
|
1776
2062
|
</button>
|
|
1777
2063
|
</div>
|
|
1778
2064
|
<div class="mushi-error" style="display:none" role="alert"></div>
|
|
@@ -1942,6 +2228,30 @@ var MushiWidget = class {
|
|
|
1942
2228
|
this.render();
|
|
1943
2229
|
});
|
|
1944
2230
|
});
|
|
2231
|
+
const textarea = panel.querySelector(".mushi-textarea");
|
|
2232
|
+
const charCurrentEl = panel.querySelector('[data-role="char-current"]');
|
|
2233
|
+
if (textarea && charCurrentEl) {
|
|
2234
|
+
const minLen = this.effectiveMinLength();
|
|
2235
|
+
const updateCounter = () => {
|
|
2236
|
+
const len = textarea.value.trim().length;
|
|
2237
|
+
charCurrentEl.textContent = String(len);
|
|
2238
|
+
const counterEl = panel.querySelector('[data-role="char-counter"]');
|
|
2239
|
+
if (counterEl) {
|
|
2240
|
+
counterEl.style.color = len >= minLen ? "var(--mushi-ok, #22c55e)" : "";
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
textarea.addEventListener("input", updateCounter);
|
|
2244
|
+
}
|
|
2245
|
+
panel.querySelectorAll("[data-example]").forEach((chip) => {
|
|
2246
|
+
chip.addEventListener("click", () => {
|
|
2247
|
+
const example = chip.dataset.example ?? "";
|
|
2248
|
+
if (textarea) {
|
|
2249
|
+
textarea.value = example;
|
|
2250
|
+
textarea.focus();
|
|
2251
|
+
textarea.dispatchEvent(new Event("input"));
|
|
2252
|
+
}
|
|
2253
|
+
});
|
|
2254
|
+
});
|
|
1945
2255
|
panel.querySelector('[data-action="screenshot"]')?.addEventListener("click", () => {
|
|
1946
2256
|
this.callbacks.onScreenshotRequest();
|
|
1947
2257
|
});
|
|
@@ -1952,14 +2262,16 @@ var MushiWidget = class {
|
|
|
1952
2262
|
this.callbacks.onElementSelectorRequest?.();
|
|
1953
2263
|
});
|
|
1954
2264
|
const submitReport = () => {
|
|
1955
|
-
const
|
|
1956
|
-
const description =
|
|
2265
|
+
const textarea2 = panel.querySelector(".mushi-textarea");
|
|
2266
|
+
const description = textarea2?.value?.trim() ?? "";
|
|
1957
2267
|
const errorEl = panel.querySelector(".mushi-error");
|
|
1958
|
-
const
|
|
1959
|
-
if (description.length <
|
|
2268
|
+
const minLen = this.effectiveMinLength();
|
|
2269
|
+
if (description.length < minLen) {
|
|
1960
2270
|
if (errorEl) {
|
|
1961
|
-
|
|
2271
|
+
const msg = `${t.step3.tooShort} (${description.length}/${minLen})`;
|
|
2272
|
+
errorEl.textContent = msg;
|
|
1962
2273
|
errorEl.style.display = "block";
|
|
2274
|
+
textarea2?.focus();
|
|
1963
2275
|
}
|
|
1964
2276
|
return;
|
|
1965
2277
|
}
|
|
@@ -2622,50 +2934,16 @@ function truncateUrl(url) {
|
|
|
2622
2934
|
function createScreenshotCapture(options = {}) {
|
|
2623
2935
|
let activeOptions = options;
|
|
2624
2936
|
async function take() {
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
const
|
|
2632
|
-
|
|
2633
|
-
canvas.width = width * dpr;
|
|
2634
|
-
canvas.height = height * dpr;
|
|
2635
|
-
ctx.scale(dpr, dpr);
|
|
2636
|
-
const safeDocument = buildPrivacySafeDocument(activeOptions.privacy);
|
|
2637
|
-
const svgData = `
|
|
2638
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
|
|
2639
|
-
<foreignObject width="100%" height="100%">
|
|
2640
|
-
<div xmlns="http://www.w3.org/1999/xhtml">
|
|
2641
|
-
${new XMLSerializer().serializeToString(safeDocument)}
|
|
2642
|
-
</div>
|
|
2643
|
-
</foreignObject>
|
|
2644
|
-
</svg>
|
|
2645
|
-
`;
|
|
2646
|
-
const img = new Image();
|
|
2647
|
-
const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
|
2648
|
-
const url = URL.createObjectURL(blob);
|
|
2649
|
-
return new Promise((resolve) => {
|
|
2650
|
-
img.onload = () => {
|
|
2651
|
-
ctx.drawImage(img, 0, 0, width, height);
|
|
2652
|
-
URL.revokeObjectURL(url);
|
|
2653
|
-
try {
|
|
2654
|
-
const dataUrl = canvas.toDataURL("image/jpeg", 0.7);
|
|
2655
|
-
resolve(dataUrl);
|
|
2656
|
-
} catch {
|
|
2657
|
-
resolve(null);
|
|
2658
|
-
}
|
|
2659
|
-
};
|
|
2660
|
-
img.onerror = () => {
|
|
2661
|
-
URL.revokeObjectURL(url);
|
|
2662
|
-
resolve(null);
|
|
2663
|
-
};
|
|
2664
|
-
img.src = url;
|
|
2665
|
-
});
|
|
2666
|
-
} catch {
|
|
2667
|
-
return null;
|
|
2937
|
+
if (typeof document === "undefined") {
|
|
2938
|
+
return { ok: false, reason: "unsupported", message: "Not in a browser context" };
|
|
2939
|
+
}
|
|
2940
|
+
const svgResult = await trySvgCapture(activeOptions.privacy);
|
|
2941
|
+
if (svgResult.ok) return svgResult;
|
|
2942
|
+
if (svgResult.reason !== "unsupported") {
|
|
2943
|
+
const mediaResult = await tryDisplayMediaCapture();
|
|
2944
|
+
if (mediaResult.ok) return mediaResult;
|
|
2668
2945
|
}
|
|
2946
|
+
return svgResult;
|
|
2669
2947
|
}
|
|
2670
2948
|
return {
|
|
2671
2949
|
take,
|
|
@@ -2674,8 +2952,110 @@ function createScreenshotCapture(options = {}) {
|
|
|
2674
2952
|
}
|
|
2675
2953
|
};
|
|
2676
2954
|
}
|
|
2955
|
+
async function trySvgCapture(privacy) {
|
|
2956
|
+
try {
|
|
2957
|
+
const canvas = document.createElement("canvas");
|
|
2958
|
+
const ctx = canvas.getContext("2d");
|
|
2959
|
+
if (!ctx) return { ok: false, reason: "unsupported", message: "Canvas 2d context unavailable" };
|
|
2960
|
+
const width = window.innerWidth;
|
|
2961
|
+
const height = window.innerHeight;
|
|
2962
|
+
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
2963
|
+
canvas.width = width * dpr;
|
|
2964
|
+
canvas.height = height * dpr;
|
|
2965
|
+
ctx.scale(dpr, dpr);
|
|
2966
|
+
const safeDocument = buildPrivacySafeDocument(privacy);
|
|
2967
|
+
const svgData = `
|
|
2968
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
|
|
2969
|
+
<foreignObject width="100%" height="100%">
|
|
2970
|
+
<div xmlns="http://www.w3.org/1999/xhtml">
|
|
2971
|
+
${new XMLSerializer().serializeToString(safeDocument)}
|
|
2972
|
+
</div>
|
|
2973
|
+
</foreignObject>
|
|
2974
|
+
</svg>
|
|
2975
|
+
`;
|
|
2976
|
+
const img = new Image();
|
|
2977
|
+
const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
|
|
2978
|
+
const url = URL.createObjectURL(blob);
|
|
2979
|
+
const loadResult = await new Promise((resolve) => {
|
|
2980
|
+
img.onload = () => resolve("loaded");
|
|
2981
|
+
img.onerror = () => resolve("error");
|
|
2982
|
+
const timeout = setTimeout(() => resolve("error"), 5e3);
|
|
2983
|
+
img.onload = () => {
|
|
2984
|
+
clearTimeout(timeout);
|
|
2985
|
+
resolve("loaded");
|
|
2986
|
+
};
|
|
2987
|
+
});
|
|
2988
|
+
URL.revokeObjectURL(url);
|
|
2989
|
+
if (loadResult === "error") {
|
|
2990
|
+
return { ok: false, reason: "load-error", message: "SVG image load failed" };
|
|
2991
|
+
}
|
|
2992
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
2993
|
+
try {
|
|
2994
|
+
const dataUrl = canvas.toDataURL("image/jpeg", 0.75);
|
|
2995
|
+
return { ok: true, dataUrl };
|
|
2996
|
+
} catch (err) {
|
|
2997
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2998
|
+
return { ok: false, reason: "tainted", message };
|
|
2999
|
+
}
|
|
3000
|
+
} catch (err) {
|
|
3001
|
+
return { ok: false, reason: "error", message: err instanceof Error ? err.message : String(err) };
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
async function tryDisplayMediaCapture() {
|
|
3005
|
+
if (typeof navigator === "undefined" || !("mediaDevices" in navigator)) {
|
|
3006
|
+
return { ok: false, reason: "unsupported", message: "mediaDevices not available" };
|
|
3007
|
+
}
|
|
3008
|
+
const mediaDevices = navigator.mediaDevices;
|
|
3009
|
+
if (typeof mediaDevices.getDisplayMedia !== "function") {
|
|
3010
|
+
return { ok: false, reason: "unsupported", message: "getDisplayMedia not available" };
|
|
3011
|
+
}
|
|
3012
|
+
let stream = null;
|
|
3013
|
+
try {
|
|
3014
|
+
stream = await mediaDevices.getDisplayMedia({
|
|
3015
|
+
video: { displaySurface: "browser" },
|
|
3016
|
+
audio: false
|
|
3017
|
+
});
|
|
3018
|
+
const track = stream.getVideoTracks()[0];
|
|
3019
|
+
if (!track) return { ok: false, reason: "error", message: "No video track" };
|
|
3020
|
+
const imageCapture = new window.ImageCapture(track);
|
|
3021
|
+
const bitmap = await imageCapture.grabFrame();
|
|
3022
|
+
const canvas = document.createElement("canvas");
|
|
3023
|
+
canvas.width = bitmap.width;
|
|
3024
|
+
canvas.height = bitmap.height;
|
|
3025
|
+
const ctx = canvas.getContext("2d");
|
|
3026
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
3027
|
+
bitmap.close();
|
|
3028
|
+
const dataUrl = canvas.toDataURL("image/jpeg", 0.85);
|
|
3029
|
+
return { ok: true, dataUrl };
|
|
3030
|
+
} catch (err) {
|
|
3031
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3032
|
+
if (err instanceof Error && err.name === "NotAllowedError") {
|
|
3033
|
+
return { ok: false, reason: "cancelled", message };
|
|
3034
|
+
}
|
|
3035
|
+
return { ok: false, reason: "error", message };
|
|
3036
|
+
} finally {
|
|
3037
|
+
stream?.getTracks().forEach((t) => t.stop());
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
2677
3040
|
function buildPrivacySafeDocument(privacy) {
|
|
2678
3041
|
const clone = document.documentElement.cloneNode(true);
|
|
3042
|
+
for (const img of Array.from(clone.querySelectorAll("img[src]"))) {
|
|
3043
|
+
const src = img.getAttribute("src") ?? "";
|
|
3044
|
+
try {
|
|
3045
|
+
const url = new URL(src, window.location.href);
|
|
3046
|
+
if (url.origin !== window.location.origin) {
|
|
3047
|
+
img.removeAttribute("src");
|
|
3048
|
+
img.removeAttribute("srcset");
|
|
3049
|
+
}
|
|
3050
|
+
} catch {
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
for (const el of Array.from(clone.querySelectorAll("[style]"))) {
|
|
3054
|
+
const style = el.getAttribute("style") ?? "";
|
|
3055
|
+
if (/url\(["']?https?:\/\/(?!localhost)/.test(style)) {
|
|
3056
|
+
el.setAttribute("style", style.replace(/url\([^)]*\)/g, "none"));
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
2679
3059
|
for (const selector of privacy?.blockSelectors ?? []) {
|
|
2680
3060
|
for (const el of safeQueryAll(clone, selector)) {
|
|
2681
3061
|
el.remove();
|
|
@@ -2710,6 +3090,15 @@ function maskElement(el) {
|
|
|
2710
3090
|
}
|
|
2711
3091
|
|
|
2712
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
|
+
}
|
|
2713
3102
|
function createPerformanceCapture() {
|
|
2714
3103
|
const metrics = {};
|
|
2715
3104
|
const observers = [];
|
|
@@ -2761,6 +3150,53 @@ function createPerformanceCapture() {
|
|
|
2761
3150
|
observers.push(longTaskObserver);
|
|
2762
3151
|
} catch {
|
|
2763
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
|
+
}
|
|
2764
3200
|
}
|
|
2765
3201
|
if (typeof performance !== "undefined" && performance.getEntriesByType) {
|
|
2766
3202
|
try {
|
|
@@ -2872,6 +3308,11 @@ function createElementSelector() {
|
|
|
2872
3308
|
e.stopPropagation();
|
|
2873
3309
|
const target = e.target;
|
|
2874
3310
|
if (target === overlay) return;
|
|
3311
|
+
const path = e.composedPath ? e.composedPath() : [];
|
|
3312
|
+
const hitsMushiHost = path.some(
|
|
3313
|
+
(node) => node instanceof Element && node.id === "mushi-mushi-widget"
|
|
3314
|
+
);
|
|
3315
|
+
if (hitsMushiHost) return;
|
|
2875
3316
|
const captured = captureElement(target);
|
|
2876
3317
|
finish(captured);
|
|
2877
3318
|
}
|
|
@@ -3586,7 +4027,7 @@ function createProactiveManager(config = {}) {
|
|
|
3586
4027
|
|
|
3587
4028
|
// src/version.ts
|
|
3588
4029
|
var MUSHI_SDK_PACKAGE = "@mushi-mushi/web";
|
|
3589
|
-
var MUSHI_SDK_VERSION = "1.
|
|
4030
|
+
var MUSHI_SDK_VERSION = "1.5.0" ;
|
|
3590
4031
|
|
|
3591
4032
|
// src/mushi.ts
|
|
3592
4033
|
var instance = null;
|
|
@@ -3791,8 +4232,21 @@ function createInstance(config) {
|
|
|
3791
4232
|
onScreenshotRequest: async () => {
|
|
3792
4233
|
if (!screenshotCap || activeConfig.capture?.screenshot === "off") return;
|
|
3793
4234
|
log.debug("Taking screenshot");
|
|
3794
|
-
|
|
3795
|
-
|
|
4235
|
+
widget.setScreenshotCapturing(true);
|
|
4236
|
+
const result = await screenshotCap.take();
|
|
4237
|
+
if (result.ok) {
|
|
4238
|
+
pendingScreenshot = result.dataUrl;
|
|
4239
|
+
widget.setScreenshotAttached(true);
|
|
4240
|
+
log.debug("Screenshot captured");
|
|
4241
|
+
} else {
|
|
4242
|
+
pendingScreenshot = null;
|
|
4243
|
+
if (result.reason !== "cancelled") {
|
|
4244
|
+
widget.setScreenshotError(true);
|
|
4245
|
+
log.debug("Screenshot failed", { reason: result.reason, message: result.message });
|
|
4246
|
+
} else {
|
|
4247
|
+
widget.setScreenshotCapturing(false);
|
|
4248
|
+
}
|
|
4249
|
+
}
|
|
3796
4250
|
},
|
|
3797
4251
|
onScreenshotRemove: () => {
|
|
3798
4252
|
log.debug("Screenshot attachment removed");
|
|
@@ -3802,11 +4256,19 @@ function createInstance(config) {
|
|
|
3802
4256
|
onElementSelectorRequest: async () => {
|
|
3803
4257
|
if (!elementSelector || activeConfig.capture?.elementSelector === false) return;
|
|
3804
4258
|
log.debug("Element selector activated");
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
4259
|
+
widget.setElementCapturing(true);
|
|
4260
|
+
widget.hidePanel();
|
|
4261
|
+
try {
|
|
4262
|
+
const el = await elementSelector.activate();
|
|
4263
|
+
if (el) {
|
|
4264
|
+
pendingElement = el;
|
|
4265
|
+
widget.setElementSelected(true);
|
|
4266
|
+
log.debug("Element selected", { tagName: el.tagName, xpath: el.xpath });
|
|
4267
|
+
} else {
|
|
4268
|
+
widget.setElementCapturing(false);
|
|
4269
|
+
}
|
|
4270
|
+
} finally {
|
|
4271
|
+
widget.showPanel();
|
|
3810
4272
|
}
|
|
3811
4273
|
},
|
|
3812
4274
|
async onReporterReportsRequest() {
|
|
@@ -4042,17 +4504,43 @@ function createInstance(config) {
|
|
|
4042
4504
|
};
|
|
4043
4505
|
config.integrations.custom(builder);
|
|
4044
4506
|
}
|
|
4045
|
-
|
|
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 });
|
|
4046
4530
|
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
|
4047
|
-
await offlineQueue.enqueue(
|
|
4048
|
-
log.info("Offline \u2014 report queued", { reportId:
|
|
4049
|
-
emit("report:queued", { reportId:
|
|
4531
|
+
await offlineQueue.enqueue(finalReport);
|
|
4532
|
+
log.info("Offline \u2014 report queued", { reportId: finalReport.id });
|
|
4533
|
+
emit("report:queued", { reportId: finalReport.id });
|
|
4050
4534
|
return;
|
|
4051
4535
|
}
|
|
4052
|
-
const result = await apiClient2.submitReport(
|
|
4536
|
+
const result = await apiClient2.submitReport(finalReport);
|
|
4053
4537
|
if (result.ok) {
|
|
4054
4538
|
log.info("Report sent", { reportId: result.data?.reportId });
|
|
4055
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
|
+
}
|
|
4056
4544
|
breadcrumbs.add({
|
|
4057
4545
|
category: "lifecycle",
|
|
4058
4546
|
level: "info",
|
|
@@ -4069,13 +4557,13 @@ function createInstance(config) {
|
|
|
4069
4557
|
} catch {
|
|
4070
4558
|
}
|
|
4071
4559
|
} else {
|
|
4072
|
-
log.warn("Report failed, queuing for retry", { reportId:
|
|
4073
|
-
await offlineQueue.enqueue(
|
|
4074
|
-
emit("report:failed", { reportId:
|
|
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 });
|
|
4075
4563
|
breadcrumbs.add({
|
|
4076
4564
|
category: "lifecycle",
|
|
4077
4565
|
level: "warning",
|
|
4078
|
-
message: `Mushi report queued for retry (${
|
|
4566
|
+
message: `Mushi report queued for retry (${finalReport.id})`
|
|
4079
4567
|
});
|
|
4080
4568
|
}
|
|
4081
4569
|
pendingScreenshot = null;
|
|
@@ -4337,6 +4825,35 @@ function createInstance(config) {
|
|
|
4337
4825
|
if (typeof globalThis !== "undefined" && (bootstrapConfig.debug ?? false)) {
|
|
4338
4826
|
exposeMarketingRecorder(widget);
|
|
4339
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
|
+
}
|
|
4340
4857
|
return sdk;
|
|
4341
4858
|
}
|
|
4342
4859
|
function mergeRuntimeConfig(config, runtime) {
|