glitchgrab 1.38.0 → 1.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1206,6 +1206,19 @@ function AnnotationCanvas({ imageSrc, onSave, onCancel }) {
1206
1206
  }
1207
1207
  );
1208
1208
  }
1209
+ var SEVERITY_LEVELS = [
1210
+ "low",
1211
+ "medium",
1212
+ "high",
1213
+ "critical"
1214
+ ];
1215
+ var SEVERITY_LABELS = {
1216
+ low: "Low",
1217
+ medium: "Medium",
1218
+ high: "High",
1219
+ critical: "Critical"
1220
+ };
1221
+ var MAX_ASSIST_IMAGES = 3;
1209
1222
  function getTypeLabel(type) {
1210
1223
  switch (type) {
1211
1224
  case "RATING":
@@ -1300,7 +1313,8 @@ function Avatar({
1300
1313
  function AssistSheet({
1301
1314
  assist,
1302
1315
  theme: t2,
1303
- screenshot,
1316
+ screenshots,
1317
+ onAddImages,
1304
1318
  attachmentCount,
1305
1319
  context,
1306
1320
  reportTypeLabel,
@@ -1317,6 +1331,8 @@ function AssistSheet({
1317
1331
  severity,
1318
1332
  onSeverityChange,
1319
1333
  showSeverity,
1334
+ validationError,
1335
+ severityRefused = false,
1320
1336
  onDuplicateChange,
1321
1337
  onConversationChange,
1322
1338
  isSubmitting,
@@ -1327,6 +1343,7 @@ function AssistSheet({
1327
1343
  onFinish
1328
1344
  }) {
1329
1345
  const narrow = useIsNarrow();
1346
+ const severityAsked = showSeverity && currentTile === "BUG";
1330
1347
  const [messages, setMessages] = (0, import_react3.useState)([]);
1331
1348
  const [input, setInput] = (0, import_react3.useState)("");
1332
1349
  const [busy, setBusy] = (0, import_react3.useState)(false);
@@ -1351,11 +1368,18 @@ function AssistSheet({
1351
1368
  if (phase === "draft") (_a = draftRef.current) == null ? void 0 : _a.focus();
1352
1369
  }, [phase]);
1353
1370
  async function runTurn(history2) {
1354
- var _a, _b, _c, _d;
1371
+ var _a, _b, _c, _d, _e;
1355
1372
  setBusy(true);
1356
1373
  let result;
1357
1374
  try {
1358
- result = await assist({ messages: history2, conversationId, screenshot, context });
1375
+ const sent = screenshots.slice(-MAX_ASSIST_IMAGES);
1376
+ result = await assist({
1377
+ messages: history2,
1378
+ conversationId,
1379
+ screenshots: sent,
1380
+ screenshot: (_a = sent[sent.length - 1]) != null ? _a : null,
1381
+ context
1382
+ });
1359
1383
  } catch (e) {
1360
1384
  onDegrade("The assistant is unavailable \u2014 write your report below and send it as normal.");
1361
1385
  return;
@@ -1370,8 +1394,8 @@ function AssistSheet({
1370
1394
  setConversationId(result.conversationId);
1371
1395
  onConversationChange == null ? void 0 : onConversationChange(result.conversationId);
1372
1396
  }
1373
- setDuplicate((_a = result.duplicate) != null ? _a : null);
1374
- onDuplicateChange == null ? void 0 : onDuplicateChange((_c = (_b = result.duplicate) == null ? void 0 : _b.number) != null ? _c : null);
1397
+ setDuplicate((_b = result.duplicate) != null ? _b : null);
1398
+ onDuplicateChange == null ? void 0 : onDuplicateChange((_d = (_c = result.duplicate) == null ? void 0 : _c.number) != null ? _d : null);
1375
1399
  if (result.solved) {
1376
1400
  setSolved(result.solved);
1377
1401
  setOptions([]);
@@ -1387,7 +1411,7 @@ function AssistSheet({
1387
1411
  }
1388
1412
  if (result.question) {
1389
1413
  setMessages([...history2, { role: "assistant", content: result.question }]);
1390
- setOptions((_d = result.options) != null ? _d : []);
1414
+ setOptions((_e = result.options) != null ? _e : []);
1391
1415
  return;
1392
1416
  }
1393
1417
  onDegrade("The assistant had nothing to add \u2014 write your report below and send it as normal.");
@@ -1997,8 +2021,8 @@ function AssistSheet({
1997
2021
  }
1998
2022
  }
1999
2023
  ),
2000
- showSeverity && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
2001
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2024
+ severityAsked && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
2025
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2002
2026
  "span",
2003
2027
  {
2004
2028
  style: {
@@ -2007,36 +2031,60 @@ function AssistSheet({
2007
2031
  display: "block",
2008
2032
  marginBottom: "6px"
2009
2033
  },
2010
- children: "How bad is it?"
2034
+ children: [
2035
+ "How bad is it?",
2036
+ !severity && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2037
+ "span",
2038
+ {
2039
+ style: {
2040
+ marginLeft: "6px",
2041
+ fontSize: "11px",
2042
+ color: severityRefused ? "#ef4444" : t2.textMuted
2043
+ },
2044
+ children: "\xB7 required"
2045
+ }
2046
+ )
2047
+ ]
2011
2048
  }
2012
2049
  ),
2013
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: "6px" }, children: ["low", "medium", "high"].map((s) => {
2014
- const on = severity === s;
2015
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2016
- "button",
2017
- {
2018
- type: "button",
2019
- onClick: () => onSeverityChange(s),
2020
- "aria-pressed": on,
2021
- style: {
2022
- flex: 1,
2023
- minWidth: 0,
2024
- padding: "8px 0",
2025
- borderRadius: "8px",
2026
- border: `1px solid ${on ? t2.accent : t2.inputBorder}`,
2027
- backgroundColor: on ? t2.accent : "transparent",
2028
- color: on ? t2.accentText : t2.textMuted,
2029
- fontSize: "12px",
2030
- fontWeight: 600,
2031
- fontFamily: "inherit",
2032
- textTransform: "capitalize",
2033
- cursor: "pointer"
2034
- },
2035
- children: s
2036
- },
2037
- s
2038
- );
2039
- }) })
2050
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2051
+ "div",
2052
+ {
2053
+ role: "radiogroup",
2054
+ "aria-label": "Severity",
2055
+ style: { display: "flex", gap: "6px" },
2056
+ children: SEVERITY_LEVELS.map((s) => {
2057
+ const on = severity === s;
2058
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2059
+ "button",
2060
+ {
2061
+ type: "button",
2062
+ onClick: () => onSeverityChange(s),
2063
+ role: "radio",
2064
+ "aria-checked": on,
2065
+ style: {
2066
+ flex: 1,
2067
+ minWidth: 0,
2068
+ padding: "8px 2px",
2069
+ borderRadius: "8px",
2070
+ border: `1px solid ${on ? t2.accent : severityRefused ? "#ef4444" : t2.inputBorder}`,
2071
+ backgroundColor: on ? t2.accent : "transparent",
2072
+ color: on ? t2.accentText : t2.textMuted,
2073
+ fontSize: "12px",
2074
+ fontWeight: 600,
2075
+ fontFamily: "inherit",
2076
+ whiteSpace: "nowrap",
2077
+ overflow: "hidden",
2078
+ textOverflow: "ellipsis",
2079
+ cursor: "pointer"
2080
+ },
2081
+ children: SEVERITY_LABELS[s]
2082
+ },
2083
+ s
2084
+ );
2085
+ })
2086
+ }
2087
+ )
2040
2088
  ] }),
2041
2089
  attachmentCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { fontSize: "11.5px", color: t2.textMuted }, children: [
2042
2090
  attachmentCount,
@@ -2166,60 +2214,16 @@ function AssistSheet({
2166
2214
  },
2167
2215
  children: isSubmitting ? "Sending\u2026" : rating < 1 ? "Pick a star first" : "Send Rating"
2168
2216
  }
2169
- ) : phase === "draft" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2170
- "button",
2171
- {
2172
- type: "button",
2173
- "data-gg-sheet-send": "",
2174
- onClick: onSend,
2175
- disabled: isSubmitting || !description.trim(),
2176
- style: {
2177
- width: "100%",
2178
- padding: "12px",
2179
- borderRadius: "10px",
2180
- border: "none",
2181
- backgroundColor: isSubmitting || !description.trim() ? t2.bgSecondary : t2.accent,
2182
- color: isSubmitting || !description.trim() ? t2.textMuted : t2.accentText,
2183
- fontSize: "14px",
2184
- fontWeight: 700,
2185
- fontFamily: "inherit",
2186
- cursor: isSubmitting || !description.trim() ? "default" : "pointer"
2187
- },
2188
- children: isSubmitting ? "Sending\u2026" : duplicate ? `Add to #${duplicate.number}` : "Send Report"
2189
- }
2190
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "flex-end" }, children: [
2191
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2192
- "textarea",
2217
+ ) : phase === "draft" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2218
+ validationError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2219
+ "p",
2193
2220
  {
2194
- ref: inputRef,
2195
- value: input,
2196
- onChange: (e) => setInput(e.target.value),
2197
- onKeyDown: (e) => {
2198
- if (e.key === "Enter" && !e.shiftKey) {
2199
- e.preventDefault();
2200
- send(input);
2201
- }
2202
- },
2203
- placeholder: "Type your answer\u2026",
2204
- rows: 1,
2205
- disabled: busy,
2206
- autoFocus: true,
2207
2221
  style: {
2208
- flex: 1,
2209
- minHeight: "42px",
2210
- maxHeight: "120px",
2211
- padding: "11px 12px",
2212
- borderRadius: "10px",
2213
- border: `1px solid ${t2.inputBorder}`,
2214
- backgroundColor: t2.inputBg,
2215
- color: t2.text,
2216
- fontSize: "13px",
2217
- lineHeight: 1.4,
2218
- fontFamily: "inherit",
2219
- resize: "none",
2220
- outline: "none",
2221
- boxSizing: "border-box"
2222
- }
2222
+ margin: "0 0 8px",
2223
+ color: "#ef4444",
2224
+ fontSize: "12px"
2225
+ },
2226
+ children: validationError
2223
2227
  }
2224
2228
  ),
2225
2229
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -2227,32 +2231,168 @@ function AssistSheet({
2227
2231
  {
2228
2232
  type: "button",
2229
2233
  "data-gg-sheet-send": "",
2230
- onClick: () => send(input),
2231
- disabled: busy || !input.trim(),
2232
- "aria-label": "Send message",
2233
- "data-gg-send-message": "",
2234
+ onClick: onSend,
2235
+ disabled: isSubmitting || !description.trim(),
2234
2236
  style: {
2235
- flexShrink: 0,
2236
- width: "42px",
2237
- height: "42px",
2237
+ width: "100%",
2238
+ padding: "12px",
2238
2239
  borderRadius: "10px",
2239
2240
  border: "none",
2240
- display: "inline-flex",
2241
- alignItems: "center",
2242
- justifyContent: "center",
2243
- backgroundColor: busy || !input.trim() ? t2.bgSecondary : t2.accent,
2244
- color: busy || !input.trim() ? t2.textMuted : t2.accentText,
2245
- cursor: busy || !input.trim() ? "default" : "pointer"
2241
+ backgroundColor: isSubmitting || !description.trim() ? t2.bgSecondary : t2.accent,
2242
+ color: isSubmitting || !description.trim() ? t2.textMuted : t2.accentText,
2243
+ fontSize: "14px",
2244
+ fontWeight: 700,
2245
+ fontFamily: "inherit",
2246
+ cursor: isSubmitting || !description.trim() ? "default" : "pointer"
2246
2247
  },
2247
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2248
- "path",
2249
- {
2250
- d: "M3 20.5L21.5 12 3 3.5V10l12 2-12 2v6.5z",
2251
- fill: "currentColor"
2252
- }
2253
- ) })
2248
+ children: isSubmitting ? "Sending\u2026" : duplicate ? `Add to #${duplicate.number}` : "Send Report"
2254
2249
  }
2255
2250
  )
2251
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2252
+ (screenshots.length > 0 || onAddImages) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2253
+ "div",
2254
+ {
2255
+ style: {
2256
+ display: "flex",
2257
+ gap: "8px",
2258
+ alignItems: "center",
2259
+ flexWrap: "wrap",
2260
+ marginBottom: "8px"
2261
+ },
2262
+ children: [
2263
+ screenshots.map((shot, i) => {
2264
+ const read = i >= screenshots.length - MAX_ASSIST_IMAGES;
2265
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2266
+ "img",
2267
+ {
2268
+ src: shot,
2269
+ alt: read ? "Attached image the assistant reads" : "Attached image",
2270
+ "data-gg-assist-thumb": "",
2271
+ style: {
2272
+ width: "40px",
2273
+ height: "40px",
2274
+ objectFit: "cover",
2275
+ borderRadius: "8px",
2276
+ border: `1px solid ${read ? t2.accent : t2.inputBorder}`,
2277
+ opacity: read ? 1 : 0.45
2278
+ }
2279
+ },
2280
+ `${i}-${shot.slice(-16)}`
2281
+ );
2282
+ }),
2283
+ onAddImages && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2284
+ "label",
2285
+ {
2286
+ "data-gg-assist-attach": "",
2287
+ title: "Attach an image \u2014 or just paste one",
2288
+ style: {
2289
+ width: "40px",
2290
+ height: "40px",
2291
+ borderRadius: "8px",
2292
+ border: `1px dashed ${t2.inputBorder}`,
2293
+ display: "inline-flex",
2294
+ alignItems: "center",
2295
+ justifyContent: "center",
2296
+ color: t2.textMuted,
2297
+ fontSize: "18px",
2298
+ cursor: "pointer",
2299
+ flexShrink: 0
2300
+ },
2301
+ children: [
2302
+ "+",
2303
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2304
+ "input",
2305
+ {
2306
+ type: "file",
2307
+ accept: "image/*",
2308
+ multiple: true,
2309
+ onChange: (e) => {
2310
+ var _a;
2311
+ const files = Array.from((_a = e.target.files) != null ? _a : []);
2312
+ if (files.length) onAddImages(files);
2313
+ e.target.value = "";
2314
+ },
2315
+ style: { display: "none" }
2316
+ }
2317
+ )
2318
+ ]
2319
+ }
2320
+ ),
2321
+ screenshots.length > MAX_ASSIST_IMAGES && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { color: t2.textMuted, fontSize: "11px" }, children: [
2322
+ "newest ",
2323
+ MAX_ASSIST_IMAGES,
2324
+ " read"
2325
+ ] })
2326
+ ]
2327
+ }
2328
+ ),
2329
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: "8px", alignItems: "flex-end" }, children: [
2330
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2331
+ "textarea",
2332
+ {
2333
+ ref: inputRef,
2334
+ value: input,
2335
+ onChange: (e) => setInput(e.target.value),
2336
+ onKeyDown: (e) => {
2337
+ if (e.key === "Enter" && !e.shiftKey) {
2338
+ e.preventDefault();
2339
+ send(input);
2340
+ }
2341
+ },
2342
+ placeholder: "Type your answer\u2026",
2343
+ rows: 1,
2344
+ disabled: busy,
2345
+ autoFocus: true,
2346
+ style: {
2347
+ flex: 1,
2348
+ minHeight: "42px",
2349
+ maxHeight: "120px",
2350
+ padding: "11px 12px",
2351
+ borderRadius: "10px",
2352
+ border: `1px solid ${t2.inputBorder}`,
2353
+ backgroundColor: t2.inputBg,
2354
+ color: t2.text,
2355
+ fontSize: "13px",
2356
+ lineHeight: 1.4,
2357
+ fontFamily: "inherit",
2358
+ resize: "none",
2359
+ outline: "none",
2360
+ boxSizing: "border-box"
2361
+ }
2362
+ }
2363
+ ),
2364
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2365
+ "button",
2366
+ {
2367
+ type: "button",
2368
+ "data-gg-sheet-send": "",
2369
+ onClick: () => send(input),
2370
+ disabled: busy || !input.trim(),
2371
+ "aria-label": "Send message",
2372
+ "data-gg-send-message": "",
2373
+ style: {
2374
+ flexShrink: 0,
2375
+ width: "42px",
2376
+ height: "42px",
2377
+ borderRadius: "10px",
2378
+ border: "none",
2379
+ display: "inline-flex",
2380
+ alignItems: "center",
2381
+ justifyContent: "center",
2382
+ backgroundColor: busy || !input.trim() ? t2.bgSecondary : t2.accent,
2383
+ color: busy || !input.trim() ? t2.textMuted : t2.accentText,
2384
+ cursor: busy || !input.trim() ? "default" : "pointer"
2385
+ },
2386
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "17", height: "17", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2387
+ "path",
2388
+ {
2389
+ d: "M3 20.5L21.5 12 3 3.5V10l12 2-12 2v6.5z",
2390
+ fill: "currentColor"
2391
+ }
2392
+ ) })
2393
+ }
2394
+ )
2395
+ ] })
2256
2396
  ] }),
2257
2397
  phase === "chat" && messages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2258
2398
  "button",
@@ -2331,6 +2471,7 @@ var ATTACHMENT_ACCEPT = [
2331
2471
  ".xlsx",
2332
2472
  ".zip"
2333
2473
  ].join(",");
2474
+ var SEVERITY_REQUIRED = "Pick how bad it is before sending.";
2334
2475
  function useIsDark() {
2335
2476
  if (typeof window === "undefined") return true;
2336
2477
  try {
@@ -2625,6 +2766,34 @@ async function captureViaHtml2Canvas() {
2625
2766
  return null;
2626
2767
  }
2627
2768
  }
2769
+ var MIC_MESSAGES = {
2770
+ blocked: "Microphone is blocked for this site. Allow it in your browser's site settings, then reload this page. You can type your report instead.",
2771
+ notFound: "No microphone found. Connect one and try again, or type your report instead.",
2772
+ inUse: "Microphone is in use by another app. Close it and try again, or type your report instead.",
2773
+ unusable: "This microphone can't be used for recording. Pick another one in your system sound settings.",
2774
+ insecure: "Voice needs a secure (https) connection. Type your report instead.",
2775
+ generic: "Couldn't start the microphone. Try again, or type your report instead."
2776
+ };
2777
+ var micErrorMessage = (err) => {
2778
+ const name = typeof err === "object" && err !== null && "name" in err ? String(err.name) : "";
2779
+ switch (name) {
2780
+ case "NotAllowedError":
2781
+ case "PermissionDeniedError":
2782
+ case "SecurityError":
2783
+ return MIC_MESSAGES.blocked;
2784
+ case "NotFoundError":
2785
+ case "DevicesNotFoundError":
2786
+ return MIC_MESSAGES.notFound;
2787
+ case "NotReadableError":
2788
+ case "TrackStartError":
2789
+ return MIC_MESSAGES.inUse;
2790
+ case "OverconstrainedError":
2791
+ case "ConstraintNotSatisfiedError":
2792
+ return MIC_MESSAGES.unusable;
2793
+ default:
2794
+ return null;
2795
+ }
2796
+ };
2628
2797
  function ReportDialog({
2629
2798
  report,
2630
2799
  sendFeedback: sendFeedback2,
@@ -2639,7 +2808,7 @@ function ReportDialog({
2639
2808
  onClose,
2640
2809
  headerSlot
2641
2810
  }) {
2642
- var _a, _b;
2811
+ var _a;
2643
2812
  const [isEnhancing, setIsEnhancing] = (0, import_react.useState)(false);
2644
2813
  const [assistOpen, setAssistOpen] = (0, import_react.useState)(false);
2645
2814
  const [assistNotice, setAssistNotice] = (0, import_react.useState)(null);
@@ -2731,13 +2900,14 @@ function ReportDialog({
2731
2900
  }, [isOpen]);
2732
2901
  const [step, setStep] = (0, import_react.useState)(1);
2733
2902
  const [reportType, setReportType] = (0, import_react.useState)("BUG");
2734
- const [severity, setSeverity] = (0, import_react.useState)("medium");
2903
+ const [severity, setSeverity] = (0, import_react.useState)(null);
2735
2904
  const [validationError, setValidationError] = (0, import_react.useState)(null);
2736
2905
  const [placeholderIdx, setPlaceholderIdx] = (0, import_react.useState)(0);
2737
2906
  const [rating, setRating] = (0, import_react.useState)(0);
2738
2907
  const [hoveredStar, setHoveredStar] = (0, import_react.useState)(0);
2739
2908
  const isRating = reportType === "RATING";
2740
2909
  const submitDisabled = isSubmitting || (isRating ? rating < 1 : !description.trim());
2910
+ const needsSeverity = validationError === SEVERITY_REQUIRED && showSeverity && reportType === "BUG" && !severity;
2741
2911
  const availableTypes = types != null ? types : [
2742
2912
  "BUG",
2743
2913
  "FEATURE_REQUEST",
@@ -2778,11 +2948,11 @@ function ReportDialog({
2778
2948
  return () => window.removeEventListener("keydown", onKey);
2779
2949
  }, [isOpen, step, sendFeedback2, availableTypes.join(",")]);
2780
2950
  const handleTileGridKeyDown = (e) => {
2781
- var _a2, _b2, _c;
2951
+ var _a2, _b, _c;
2782
2952
  const keys = ["ArrowRight", "ArrowLeft", "ArrowDown", "ArrowUp"];
2783
2953
  if (!keys.includes(e.key)) return;
2784
2954
  const tiles = Array.from(
2785
- (_b2 = (_a2 = tileGridRef.current) == null ? void 0 : _a2.querySelectorAll("button[data-gg-type]")) != null ? _b2 : []
2955
+ (_b = (_a2 = tileGridRef.current) == null ? void 0 : _a2.querySelectorAll("button[data-gg-type]")) != null ? _b : []
2786
2956
  );
2787
2957
  if (tiles.length === 0) return;
2788
2958
  e.preventDefault();
@@ -2917,9 +3087,9 @@ function ReportDialog({
2917
3087
  el.scrollTop = start.top - dy;
2918
3088
  };
2919
3089
  const handlePanEnd = (e) => {
2920
- var _a2, _b2;
3090
+ var _a2, _b;
2921
3091
  const el = previewScrollRef.current;
2922
- const moved = (_b2 = (_a2 = panRef.current) == null ? void 0 : _a2.moved) != null ? _b2 : false;
3092
+ const moved = (_b = (_a2 = panRef.current) == null ? void 0 : _a2.moved) != null ? _b : false;
2923
3093
  try {
2924
3094
  el == null ? void 0 : el.releasePointerCapture(e.pointerId);
2925
3095
  } catch (e2) {
@@ -2980,7 +3150,7 @@ function ReportDialog({
2980
3150
  setDescription(text);
2981
3151
  };
2982
3152
  const stopVoice = () => {
2983
- var _a2, _b2;
3153
+ var _a2, _b;
2984
3154
  usingWebSpeechRef.current = false;
2985
3155
  (_a2 = recognitionRef.current) == null ? void 0 : _a2.stop();
2986
3156
  recognitionRef.current = null;
@@ -2989,7 +3159,7 @@ function ReportDialog({
2989
3159
  streamRef.current = null;
2990
3160
  stream == null ? void 0 : stream.getTracks().forEach((t22) => t22.stop());
2991
3161
  try {
2992
- (_b2 = mediaRecorderRef.current) == null ? void 0 : _b2.stop();
3162
+ (_b = mediaRecorderRef.current) == null ? void 0 : _b.stop();
2993
3163
  } catch (e) {
2994
3164
  }
2995
3165
  mediaRecorderRef.current = null;
@@ -2997,13 +3167,13 @@ function ReportDialog({
2997
3167
  setIsTranscribing(false);
2998
3168
  };
2999
3169
  const stopListeningAndTranscribe = () => {
3000
- var _a2, _b2;
3170
+ var _a2, _b;
3001
3171
  usingWebSpeechRef.current = false;
3002
3172
  (_a2 = recognitionRef.current) == null ? void 0 : _a2.stop();
3003
3173
  recognitionRef.current = null;
3004
3174
  setIsListening(false);
3005
3175
  try {
3006
- (_b2 = mediaRecorderRef.current) == null ? void 0 : _b2.stop();
3176
+ (_b = mediaRecorderRef.current) == null ? void 0 : _b.stop();
3007
3177
  } catch (e) {
3008
3178
  }
3009
3179
  };
@@ -3031,7 +3201,7 @@ function ReportDialog({
3031
3201
  setReportType("BUG");
3032
3202
  setTypeChosen(false);
3033
3203
  setDuplicateIssueNumber(null);
3034
- setSeverity("medium");
3204
+ setSeverity(null);
3035
3205
  setValidationError(null);
3036
3206
  setVoiceError(null);
3037
3207
  setIsEnhanced(false);
@@ -3041,6 +3211,7 @@ function ReportDialog({
3041
3211
  setAssistNotice(null);
3042
3212
  };
3043
3213
  const toggleVoice = async () => {
3214
+ var _a2;
3044
3215
  if (isListening) {
3045
3216
  stopListeningAndTranscribe();
3046
3217
  return;
@@ -3049,12 +3220,22 @@ function ReportDialog({
3049
3220
  hadWebSpeechRef.current = false;
3050
3221
  const SpeechRec = typeof window !== "undefined" ? window.SpeechRecognition || window.webkitSpeechRecognition : void 0;
3051
3222
  let stream = null;
3223
+ const canRecord = typeof navigator !== "undefined" && !!((_a2 = navigator.mediaDevices) == null ? void 0 : _a2.getUserMedia);
3224
+ if (!canRecord && !SpeechRec) {
3225
+ setVoiceError(MIC_MESSAGES.insecure);
3226
+ return;
3227
+ }
3052
3228
  try {
3053
- stream = await navigator.mediaDevices.getUserMedia({ audio: true });
3229
+ if (canRecord)
3230
+ stream = await navigator.mediaDevices.getUserMedia({ audio: true });
3054
3231
  } catch (err) {
3055
- const isDenied = err instanceof DOMException && (err.name === "NotAllowedError" || err.name === "PermissionDeniedError");
3056
- if (!SpeechRec || isDenied) {
3057
- setVoiceError("Microphone access denied");
3232
+ const message = micErrorMessage(err);
3233
+ if (message) {
3234
+ setVoiceError(message);
3235
+ return;
3236
+ }
3237
+ if (!SpeechRec) {
3238
+ setVoiceError(MIC_MESSAGES.generic);
3058
3239
  return;
3059
3240
  }
3060
3241
  }
@@ -3068,7 +3249,7 @@ function ReportDialog({
3068
3249
  if (e.data.size > 0) sarvamChunksRef.current.push(e.data);
3069
3250
  };
3070
3251
  rec.onstop = async () => {
3071
- var _a2;
3252
+ var _a3;
3072
3253
  stream.getTracks().forEach((t22) => t22.stop());
3073
3254
  if (streamRef.current === stream) streamRef.current = null;
3074
3255
  mediaRecorderRef.current = null;
@@ -3081,7 +3262,7 @@ function ReportDialog({
3081
3262
  const text = await transcribeAudio2(blob);
3082
3263
  if (text.trim()) {
3083
3264
  const live = descriptionRef.current;
3084
- const baseline = (_a2 = lastVoiceTextRef.current) != null ? _a2 : textBeforeVoiceRef.current;
3265
+ const baseline = (_a3 = lastVoiceTextRef.current) != null ? _a3 : textBeforeVoiceRef.current;
3085
3266
  const userEdited = live !== baseline;
3086
3267
  if (userEdited && hadWebSpeechRef.current) {
3087
3268
  } else {
@@ -3140,8 +3321,9 @@ function ReportDialog({
3140
3321
  };
3141
3322
  recognition.onerror = (event) => {
3142
3323
  if (event.error === "aborted" || event.error === "no-speech") return;
3143
- const isDenied = event.error === "not-allowed" || event.error === "audio-capture";
3144
- setVoiceError(isDenied ? "Microphone access denied" : "Speech recognition not available \u2014 try Chrome");
3324
+ setVoiceError(
3325
+ event.error === "not-allowed" ? MIC_MESSAGES.blocked : event.error === "audio-capture" ? MIC_MESSAGES.notFound : "Speech recognition not available \u2014 try Chrome"
3326
+ );
3145
3327
  usingWebSpeechRef.current = false;
3146
3328
  recognitionRef.current = null;
3147
3329
  setIsListening(false);
@@ -3237,11 +3419,16 @@ function ReportDialog({
3237
3419
  setStep(2);
3238
3420
  return;
3239
3421
  }
3422
+ if (showSeverity && reportType === "BUG" && !severity) {
3423
+ setValidationError(SEVERITY_REQUIRED);
3424
+ setStep(2);
3425
+ return;
3426
+ }
3240
3427
  setIsSubmitting(true);
3241
3428
  const metadata = {};
3242
3429
  if (screenshots.length > 0) metadata.screenshots = JSON.stringify(screenshots);
3243
3430
  if (attachments.length > 0) metadata.attachments = JSON.stringify(attachments);
3244
- if (showSeverity && reportType === "BUG") {
3431
+ if (showSeverity && reportType === "BUG" && severity) {
3245
3432
  metadata.severity = severity;
3246
3433
  }
3247
3434
  if (duplicateIssueNumber) {
@@ -3894,7 +4081,7 @@ function ReportDialog({
3894
4081
  setOriginalDescription(null);
3895
4082
  }
3896
4083
  },
3897
- placeholder: isTranscribing ? "Transcribing your speech\u2026" : isListening ? "Listening\u2026 speak now" : getPlaceholder(reportType, placeholderIdx, !!transcribeAudio2),
4084
+ placeholder: isTranscribing ? "Transcribing your speech\u2026" : isListening ? "Listening\u2026 speak now" : getPlaceholder(reportType, placeholderIdx, !!transcribeAudio2 && !voiceError),
3898
4085
  style: {
3899
4086
  width: "100%",
3900
4087
  minHeight: "100px",
@@ -4151,7 +4338,7 @@ function ReportDialog({
4151
4338
  }
4152
4339
  )
4153
4340
  ] }),
4154
- transcribeAudio2 && !isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4341
+ transcribeAudio2 && !isListening && !isTranscribing && !voiceError && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4155
4342
  "div",
4156
4343
  {
4157
4344
  style: {
@@ -4269,9 +4456,11 @@ function ReportDialog({
4269
4456
  voiceError && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4270
4457
  "p",
4271
4458
  {
4459
+ role: "alert",
4272
4460
  style: {
4273
4461
  color: "#ef4444",
4274
4462
  fontSize: "11px",
4463
+ lineHeight: 1.5,
4275
4464
  marginTop: "4px",
4276
4465
  marginBottom: 0
4277
4466
  },
@@ -4683,41 +4872,80 @@ function ReportDialog({
4683
4872
  )
4684
4873
  ] }),
4685
4874
  showSeverity && reportType === "BUG" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginTop: "10px" }, children: [
4686
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4875
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
4687
4876
  "span",
4688
4877
  {
4689
4878
  style: {
4690
4879
  fontSize: "12px",
4691
4880
  color: t2.textMuted,
4692
4881
  marginBottom: "6px",
4693
- display: "block"
4882
+ display: "flex",
4883
+ alignItems: "center",
4884
+ gap: "6px"
4694
4885
  },
4695
- children: "Severity"
4886
+ children: [
4887
+ "How bad is it?",
4888
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4889
+ "span",
4890
+ {
4891
+ style: {
4892
+ color: severity ? t2.textMuted : needsSeverity ? "#ef4444" : t2.textMuted,
4893
+ fontSize: "11px"
4894
+ },
4895
+ children: severity ? "" : "\xB7 required"
4896
+ }
4897
+ )
4898
+ ]
4696
4899
  }
4697
4900
  ),
4698
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", gap: "6px" }, children: ["low", "medium", "high"].map((s) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4699
- "button",
4901
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4902
+ "div",
4700
4903
  {
4701
- type: "button",
4702
- onClick: () => setSeverity(s),
4703
- style: {
4704
- flex: 1,
4705
- padding: "6px 0",
4706
- borderRadius: "6px",
4707
- border: `1px solid ${severity === s ? t2.accent : t2.inputBorder}`,
4708
- background: severity === s ? t2.accent : "transparent",
4709
- color: severity === s ? t2.accentText : t2.text,
4710
- fontSize: "12px",
4711
- fontWeight: 600,
4712
- cursor: "pointer",
4713
- fontFamily: "inherit",
4714
- textTransform: "capitalize",
4715
- transition: "all 0.15s ease"
4716
- },
4717
- children: s
4718
- },
4719
- s
4720
- )) })
4904
+ role: "radiogroup",
4905
+ "aria-label": "Severity",
4906
+ style: { display: "flex", gap: "6px" },
4907
+ children: SEVERITY_LEVELS.map((s) => {
4908
+ const on = severity === s;
4909
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4910
+ "button",
4911
+ {
4912
+ type: "button",
4913
+ role: "radio",
4914
+ "aria-checked": on,
4915
+ onClick: () => {
4916
+ setSeverity(s);
4917
+ if (validationError)
4918
+ setValidationError(null);
4919
+ },
4920
+ style: {
4921
+ flex: 1,
4922
+ minWidth: 0,
4923
+ // 9px + 12px line + borders clears a 36px
4924
+ // tap target; 6px left it at ~29px.
4925
+ padding: "9px 2px",
4926
+ borderRadius: "6px",
4927
+ border: `1px solid ${on ? t2.accent : needsSeverity ? "#ef4444" : t2.inputBorder}`,
4928
+ background: on ? t2.accent : "transparent",
4929
+ color: on ? t2.accentText : t2.text,
4930
+ fontSize: "12px",
4931
+ fontWeight: 600,
4932
+ cursor: "pointer",
4933
+ fontFamily: "inherit",
4934
+ whiteSpace: "nowrap",
4935
+ // Four labels where there were three: clip
4936
+ // rather than spill if a host squeezes the
4937
+ // dialog below ~260px.
4938
+ overflow: "hidden",
4939
+ textOverflow: "ellipsis",
4940
+ transition: "all 0.15s ease"
4941
+ },
4942
+ children: SEVERITY_LABELS[s]
4943
+ },
4944
+ s
4945
+ );
4946
+ })
4947
+ }
4948
+ )
4721
4949
  ] }),
4722
4950
  validationError && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4723
4951
  "p",
@@ -4991,7 +5219,8 @@ function ReportDialog({
4991
5219
  accent: t2.accent,
4992
5220
  accentText: t2.accentText
4993
5221
  },
4994
- screenshot: (_a = screenshots[0]) != null ? _a : null,
5222
+ screenshots,
5223
+ onAddImages: addFiles,
4995
5224
  attachmentCount: screenshots.length + attachments.length,
4996
5225
  context: { ...assistContext != null ? assistContext : {}, reportType },
4997
5226
  reportTypeLabel: getTypeLabel(reportType),
@@ -5012,15 +5241,20 @@ function ReportDialog({
5012
5241
  if (validationError) setValidationError(null);
5013
5242
  },
5014
5243
  projectSlot: headerSlot,
5015
- reporterName: (_b = reporter == null ? void 0 : reporter.name) != null ? _b : null,
5244
+ reporterName: (_a = reporter == null ? void 0 : reporter.name) != null ? _a : null,
5016
5245
  description,
5017
5246
  onDescriptionChange: (value) => {
5018
5247
  setDescription(value);
5019
5248
  if (validationError) setValidationError(null);
5020
5249
  },
5021
5250
  severity,
5022
- onSeverityChange: setSeverity,
5251
+ onSeverityChange: (value) => {
5252
+ setSeverity(value);
5253
+ if (validationError) setValidationError(null);
5254
+ },
5023
5255
  showSeverity,
5256
+ validationError,
5257
+ severityRefused: needsSeverity,
5024
5258
  isSubmitting,
5025
5259
  submitted,
5026
5260
  onSend: () => void handleSubmit(),