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