glitchgrab 1.47.0 → 1.48.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.mjs CHANGED
@@ -14,6 +14,37 @@ import {
14
14
  // src/error-boundary.tsx
15
15
  import * as React from "react";
16
16
 
17
+ // ../report-ui/dist/assist-response.mjs
18
+ var ASSIST_OFFLINE_MESSAGE = "The assistant is unavailable \u2014 write your report below and send it as normal.";
19
+ function assistOffline() {
20
+ return { conversationId: null, question: null, report: null, degraded: ASSIST_OFFLINE_MESSAGE };
21
+ }
22
+ function toAssistTurnResult(ok, body) {
23
+ var _a, _b, _c, _d, _e, _f, _g, _h;
24
+ const envelope = body != null ? body : null;
25
+ if (!ok || !(envelope == null ? void 0 : envelope.success)) {
26
+ return {
27
+ ...assistOffline(),
28
+ // The server words why (cap, rate limit, switched off). An hourly limit
29
+ // keeps the assistant button for this report.
30
+ degraded: (_a = envelope == null ? void 0 : envelope.error) != null ? _a : ASSIST_OFFLINE_MESSAGE,
31
+ retryable: (envelope == null ? void 0 : envelope.retryable) === true
32
+ };
33
+ }
34
+ const d = (_b = envelope.data) != null ? _b : {};
35
+ return {
36
+ conversationId: (_c = d.conversationId) != null ? _c : null,
37
+ question: (_d = d.question) != null ? _d : null,
38
+ options: Array.isArray(d.options) ? d.options.filter((o) => typeof o === "string") : [],
39
+ report: (_e = d.report) != null ? _e : null,
40
+ duplicate: (_f = d.duplicate) != null ? _f : null,
41
+ related: (_g = d.related) != null ? _g : null,
42
+ solved: (_h = d.solved) != null ? _h : null,
43
+ aboutGlitchgrab: d.aboutGlitchgrab === true,
44
+ degraded: null
45
+ };
46
+ }
47
+
17
48
  // src/redact.ts
18
49
  var SENSITIVE_KEY_RE = /(pass|pwd|secret|token|auth|session|cookie|credential|apikey|api_key|access_key|private|signature|otp|pin|cvv|card|ssn|aadhaar|pan)/i;
19
50
  var EMAIL_RE = /[\w.+-]+@[\w-]+\.[\w.-]+/g;
@@ -688,13 +719,7 @@ async function findSimilarIssues(description, token, baseUrl, identity) {
688
719
  }
689
720
  }
690
721
  async function assistTurn(params, token, baseUrl) {
691
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
692
- const offline = {
693
- conversationId: null,
694
- question: null,
695
- report: null,
696
- degraded: "The assistant is unavailable \u2014 write your report below and send it as normal."
697
- };
722
+ const offline = assistOffline();
698
723
  try {
699
724
  const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/ai/report-chat`;
700
725
  const response = await fetch(url, {
@@ -705,28 +730,7 @@ async function assistTurn(params, token, baseUrl) {
705
730
  },
706
731
  body: JSON.stringify(params)
707
732
  });
708
- const envelope = await response.json().catch(() => null);
709
- if (!response.ok || !(envelope == null ? void 0 : envelope.success)) {
710
- return {
711
- conversationId: null,
712
- question: null,
713
- report: null,
714
- degraded: (_a = envelope == null ? void 0 : envelope.error) != null ? _a : offline.degraded,
715
- // An hourly limit, not a cap: the dialog keeps the assistant button.
716
- retryable: (envelope == null ? void 0 : envelope.retryable) === true
717
- };
718
- }
719
- return {
720
- conversationId: (_c = (_b = envelope.data) == null ? void 0 : _b.conversationId) != null ? _c : null,
721
- question: (_e = (_d = envelope.data) == null ? void 0 : _d.question) != null ? _e : null,
722
- options: (_g = (_f = envelope.data) == null ? void 0 : _f.options) != null ? _g : [],
723
- report: (_i = (_h = envelope.data) == null ? void 0 : _h.report) != null ? _i : null,
724
- duplicate: (_k = (_j = envelope.data) == null ? void 0 : _j.duplicate) != null ? _k : null,
725
- related: (_m = (_l = envelope.data) == null ? void 0 : _l.related) != null ? _m : null,
726
- solved: (_o = (_n = envelope.data) == null ? void 0 : _n.solved) != null ? _o : null,
727
- aboutGlitchgrab: ((_p = envelope.data) == null ? void 0 : _p.aboutGlitchgrab) === true,
728
- degraded: null
729
- };
733
+ return toAssistTurnResult(response.ok, await response.json().catch(() => null));
730
734
  } catch (e) {
731
735
  return offline;
732
736
  }
@@ -1421,6 +1425,17 @@ var STARTERS = [
1421
1425
  "It did the wrong thing",
1422
1426
  "I wish it could\u2026"
1423
1427
  ];
1428
+ var CONFIRM_OPTION = "Yes, that's it";
1429
+ function isCheckBackQuestion(text) {
1430
+ const t2 = text.trim();
1431
+ return /^so,? you['’]?re saying\b/i.test(t2) || /\b(is that right|is that correct|did i get (that|it) right|have i got (that|it) right)\s*\??\s*$/i.test(t2);
1432
+ }
1433
+ function isAffirmativeReply(text) {
1434
+ return /^(y|ya|yah|yes|yess|yeah|yep|yup|ok|okay|sure|correct|right|exactly|confirm(ed)?|that['’]?s (it|right|correct)|yes,? that['’]?s (it|right)|haan|han|ha|ho|hmm yes|👍)[\s.!👍]*$/i.test(
1435
+ text.trim()
1436
+ );
1437
+ }
1438
+ var AUTO_FILE_SECONDS = 4;
1424
1439
  var NARROW_QUERY = "(max-width: 640px)";
1425
1440
  function useIsNarrow() {
1426
1441
  const [narrow, setNarrow] = useState2(false);
@@ -1535,12 +1550,15 @@ function AssistSheet({
1535
1550
  const [duplicate, setDuplicate] = useState2(null);
1536
1551
  const [glitchgrabOffer, setGlitchgrabOffer] = useState2(false);
1537
1552
  const [solved, setSolved] = useState2(null);
1553
+ const needsSeverity = (tile) => showSeverity && tile === "BUG" && !severity;
1538
1554
  const [phase, setPhase] = useState2(
1539
- !typePicked ? "type" : currentTile === "RATING" ? "rating" : "chat"
1555
+ () => !typePicked ? "type" : currentTile === "RATING" ? "rating" : needsSeverity(currentTile) ? "severity" : "chat"
1540
1556
  );
1557
+ const [autoFileLeft, setAutoFileLeft] = useState2(null);
1541
1558
  const scrollRef = useRef2(null);
1542
1559
  const draftRef = useRef2(null);
1543
1560
  const seededRef = useRef2(false);
1561
+ const seedPendingRef = useRef2(false);
1544
1562
  const eventsRef = useRef2([]);
1545
1563
  const aiDraftRef = useRef2(null);
1546
1564
  function logEvent(type, detail) {
@@ -1580,14 +1598,33 @@ function AssistSheet({
1580
1598
  var _a;
1581
1599
  if (phase === "draft") (_a = draftRef.current) == null ? void 0 : _a.focus();
1582
1600
  }, [phase]);
1601
+ useEffect2(() => {
1602
+ if (autoFileLeft === null) return;
1603
+ if (isSubmitting || submitted || phase !== "draft") {
1604
+ setAutoFileLeft(null);
1605
+ return;
1606
+ }
1607
+ if (autoFileLeft <= 0) {
1608
+ sendDraft(true);
1609
+ return;
1610
+ }
1611
+ const id = setTimeout(() => setAutoFileLeft((n) => n === null ? null : n - 1), 1e3);
1612
+ return () => clearTimeout(id);
1613
+ }, [autoFileLeft, isSubmitting, submitted, phase]);
1614
+ function stopAutoFile(reason) {
1615
+ if (autoFileLeft === null) return;
1616
+ setAutoFileLeft(null);
1617
+ logEvent("auto_file_stopped", reason);
1618
+ }
1583
1619
  function degradeKeepingEvents(events, message, retryable) {
1584
1620
  if (events) eventsRef.current = [...events, ...eventsRef.current];
1585
1621
  flushEvents();
1586
1622
  onDegrade(message, retryable);
1587
1623
  }
1588
- async function runTurn(history2) {
1624
+ async function runTurn(history2, autoFile = false) {
1589
1625
  var _a, _b;
1590
1626
  setBusy(true);
1627
+ setAutoFileLeft(null);
1591
1628
  let result;
1592
1629
  const events = takeEvents();
1593
1630
  try {
@@ -1647,6 +1684,9 @@ function AssistSheet({
1647
1684
  setMessages([...history2, { role: "assistant", content: result.report, kind: "report" }]);
1648
1685
  setOptions([]);
1649
1686
  setPhase("draft");
1687
+ if (autoFile && !attach && !(severityAsked && !severity)) {
1688
+ setAutoFileLeft(AUTO_FILE_SECONDS);
1689
+ }
1650
1690
  return;
1651
1691
  }
1652
1692
  if (result.question) {
@@ -1660,13 +1700,34 @@ function AssistSheet({
1660
1700
  if (seededRef.current) return;
1661
1701
  seededRef.current = true;
1662
1702
  if (!typePicked || currentTile === "RATING") return;
1703
+ if (phase === "severity") {
1704
+ seedPendingRef.current = true;
1705
+ return;
1706
+ }
1707
+ seedFromDescription();
1708
+ }, []);
1709
+ function seedFromDescription() {
1663
1710
  const seed = description.trim();
1664
1711
  if (!seed) return;
1665
1712
  const history2 = [{ role: "user", content: seed }];
1666
1713
  setMessages(history2);
1667
1714
  void runTurn(history2);
1668
- }, []);
1669
- function send(text) {
1715
+ }
1716
+ function answerSeverity(value) {
1717
+ onSeverityChange(value);
1718
+ setPhase("chat");
1719
+ if (seedPendingRef.current) {
1720
+ seedPendingRef.current = false;
1721
+ seedFromDescription();
1722
+ }
1723
+ }
1724
+ function sendTyped(text) {
1725
+ var _a;
1726
+ const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
1727
+ const checkingBack = options.includes(CONFIRM_OPTION) || (lastAssistant == null ? void 0 : lastAssistant.kind) !== "report" && isCheckBackQuestion((_a = lastAssistant == null ? void 0 : lastAssistant.content) != null ? _a : "");
1728
+ send(text, checkingBack && isAffirmativeReply(text));
1729
+ }
1730
+ function send(text, autoFile = false) {
1670
1731
  const value = text.trim();
1671
1732
  if (!value || busy) return;
1672
1733
  setOptions([]);
@@ -1681,11 +1742,11 @@ function AssistSheet({
1681
1742
  const history2 = [...base, { role: "user", content: value }];
1682
1743
  setMessages(history2);
1683
1744
  setInput("");
1684
- void runTurn(history2);
1745
+ void runTurn(history2, autoFile);
1685
1746
  }
1686
1747
  function pickType(tile) {
1687
1748
  onPickType(tile);
1688
- setPhase(tile === "RATING" ? "rating" : "chat");
1749
+ setPhase(tile === "RATING" ? "rating" : needsSeverity(tile) ? "severity" : "chat");
1689
1750
  }
1690
1751
  function withCurrentDraft(history2) {
1691
1752
  const current = description.trim();
@@ -1698,9 +1759,10 @@ function AssistSheet({
1698
1759
  }
1699
1760
  return next;
1700
1761
  }
1701
- function sendDraft() {
1762
+ function sendDraft(auto = false) {
1763
+ setAutoFileLeft(null);
1702
1764
  const edited = aiDraftRef.current !== null && description.trim() !== aiDraftRef.current.trim();
1703
- logEvent("draft_sent", edited ? "edited" : "as drafted");
1765
+ logEvent("draft_sent", auto ? "auto-filed" : edited ? "edited" : "as drafted");
1704
1766
  flushEvents();
1705
1767
  onSend();
1706
1768
  }
@@ -1760,6 +1822,7 @@ function AssistSheet({
1760
1822
  @keyframes gg-sheet-in{from{transform:translateX(24px);opacity:0}to{transform:translateX(0);opacity:1}}
1761
1823
  @keyframes gg-sheet-up{from{transform:translateY(24px);opacity:0}to{transform:translateY(0);opacity:1}}
1762
1824
  @keyframes gg-sheet-fade{from{opacity:0}to{opacity:1}}
1825
+ @keyframes gg-autofile{from{transform:scaleX(0)}to{transform:scaleX(1)}}
1763
1826
  @keyframes gg-dot{0%,80%,100%{transform:translateY(0);opacity:.4}40%{transform:translateY(-3px);opacity:1}}
1764
1827
  [data-gg-sheet-send]:focus-visible,[data-gg-chip]:focus-visible,[data-gg-assist-remove]:focus-visible{outline:2px solid currentColor;outline-offset:2px}
1765
1828
  ` }),
@@ -1854,6 +1917,7 @@ function AssistSheet({
1854
1917
  ),
1855
1918
  /* @__PURE__ */ jsxs2("div", { style: { fontSize: "11.5px", color: t2.textMuted, marginTop: "3px" }, children: [
1856
1919
  phase === "type" ? null : reportTypeLabel,
1920
+ phase !== "type" && severityAsked && severity ? ` \xB7 ${SEVERITY_LABELS[severity]}` : null,
1857
1921
  projectSlot ? /* @__PURE__ */ jsx2("span", { style: { marginLeft: "6px" }, children: projectSlot }) : null
1858
1922
  ] })
1859
1923
  ] }),
@@ -1972,6 +2036,62 @@ function AssistSheet({
1972
2036
  }
1973
2037
  )
1974
2038
  ] }),
2039
+ phase === "severity" && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
2040
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "8px" }, children: [
2041
+ /* @__PURE__ */ jsx2(Avatar, { who: "assistant", theme: t2 }),
2042
+ /* @__PURE__ */ jsx2(
2043
+ "div",
2044
+ {
2045
+ style: {
2046
+ padding: "9px 12px",
2047
+ borderRadius: "12px 12px 12px 4px",
2048
+ backgroundColor: t2.bgSecondary,
2049
+ fontSize: "13px",
2050
+ lineHeight: 1.55,
2051
+ maxWidth: "88%"
2052
+ },
2053
+ children: "How bad is it?"
2054
+ }
2055
+ )
2056
+ ] }),
2057
+ /* @__PURE__ */ jsx2(
2058
+ "div",
2059
+ {
2060
+ role: "radiogroup",
2061
+ "aria-label": "Severity",
2062
+ style: {
2063
+ display: "flex",
2064
+ flexWrap: "wrap",
2065
+ gap: "6px",
2066
+ paddingLeft: "32px"
2067
+ },
2068
+ children: SEVERITY_LEVELS.map((s) => /* @__PURE__ */ jsx2(
2069
+ "button",
2070
+ {
2071
+ type: "button",
2072
+ role: "radio",
2073
+ "aria-checked": severity === s,
2074
+ "data-gg-chip": "",
2075
+ "data-gg-severity": s,
2076
+ onClick: () => answerSeverity(s),
2077
+ style: {
2078
+ border: `1px solid ${t2.inputBorder}`,
2079
+ background: "transparent",
2080
+ color: t2.textMuted,
2081
+ borderRadius: "999px",
2082
+ // Same as the picker and option chips — one chip size.
2083
+ padding: "8px 12px",
2084
+ fontSize: "12px",
2085
+ fontFamily: "inherit",
2086
+ cursor: "pointer"
2087
+ },
2088
+ children: SEVERITY_LABELS[s]
2089
+ },
2090
+ s
2091
+ ))
2092
+ }
2093
+ )
2094
+ ] }),
1975
2095
  phase === "rating" && !submitted && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
1976
2096
  /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "8px" }, children: [
1977
2097
  /* @__PURE__ */ jsx2(Avatar, { who: "assistant", theme: t2 }),
@@ -2160,7 +2280,11 @@ function AssistSheet({
2160
2280
  color: t2.text,
2161
2281
  border: live ? "none" : `1px solid ${t2.accent}66`
2162
2282
  },
2163
- children: live ? "Here's your report \u2014 have a read." : /* @__PURE__ */ jsxs2(Fragment, { children: [
2283
+ children: live ? (
2284
+ // #402: "have a read" beside a countdown reads as a
2285
+ // contradiction. Say what is about to happen instead.
2286
+ autoFileLeft !== null ? "Here's your report \u2014 I'll file it unless you want to change something." : "Here's your report \u2014 have a read."
2287
+ ) : /* @__PURE__ */ jsxs2(Fragment, { children: [
2164
2288
  /* @__PURE__ */ jsx2(
2165
2289
  "span",
2166
2290
  {
@@ -2327,7 +2451,7 @@ function AssistSheet({
2327
2451
  "data-gg-option": "",
2328
2452
  onClick: () => {
2329
2453
  logEvent("option_tap", option);
2330
- send(option);
2454
+ send(option, option === CONFIRM_OPTION);
2331
2455
  },
2332
2456
  style: {
2333
2457
  border: `1px solid ${t2.inputBorder}`,
@@ -2414,7 +2538,10 @@ function AssistSheet({
2414
2538
  {
2415
2539
  ref: draftRef,
2416
2540
  value: description,
2417
- onChange: (e) => onDescriptionChange(e.target.value),
2541
+ onChange: (e) => {
2542
+ stopAutoFile("edited the draft");
2543
+ onDescriptionChange(e.target.value);
2544
+ },
2418
2545
  rows: 6,
2419
2546
  style: {
2420
2547
  width: "100%",
@@ -2433,7 +2560,7 @@ function AssistSheet({
2433
2560
  }
2434
2561
  }
2435
2562
  ),
2436
- severityAsked && /* @__PURE__ */ jsxs2("div", { children: [
2563
+ severityAsked && autoFileLeft === null && /* @__PURE__ */ jsxs2("div", { children: [
2437
2564
  /* @__PURE__ */ jsxs2(
2438
2565
  "span",
2439
2566
  {
@@ -2555,7 +2682,7 @@ function AssistSheet({
2555
2682
  )
2556
2683
  }
2557
2684
  ),
2558
- !submitted && phase !== "type" && /* @__PURE__ */ jsxs2(
2685
+ !submitted && phase !== "type" && phase !== "severity" && /* @__PURE__ */ jsxs2(
2559
2686
  "div",
2560
2687
  {
2561
2688
  style: {
@@ -2662,6 +2789,7 @@ function AssistSheet({
2662
2789
  "aria-label": `Remove image ${i + 1}`,
2663
2790
  title: "Remove this image",
2664
2791
  onClick: () => {
2792
+ stopAutoFile("changed attachments");
2665
2793
  logEvent(
2666
2794
  "image_removed",
2667
2795
  pageShot && shot === pageShot ? "page capture" : "attached"
@@ -2772,6 +2900,7 @@ function AssistSheet({
2772
2900
  "aria-label": `Remove file ${file.name}`,
2773
2901
  title: "Remove this file",
2774
2902
  onClick: () => {
2903
+ stopAutoFile("changed attachments");
2775
2904
  logEvent("file_removed", read ? "read" : "not read");
2776
2905
  onRemoveFile(i);
2777
2906
  },
@@ -2854,6 +2983,7 @@ function AssistSheet({
2854
2983
  var _a;
2855
2984
  const picked = Array.from((_a = e.target.files) != null ? _a : []);
2856
2985
  if (picked.length) {
2986
+ stopAutoFile("changed attachments");
2857
2987
  const images = picked.filter((f) => f.type.startsWith("image/")).length;
2858
2988
  if (images) logEvent("image_added", String(images));
2859
2989
  if (picked.length > images) {
@@ -2883,11 +3013,14 @@ function AssistSheet({
2883
3013
  "textarea",
2884
3014
  {
2885
3015
  value: input,
2886
- onChange: (e) => setInput(e.target.value),
3016
+ onChange: (e) => {
3017
+ stopAutoFile("typing a change");
3018
+ setInput(e.target.value);
3019
+ },
2887
3020
  onKeyDown: (e) => {
2888
3021
  if (e.key === "Enter" && !e.shiftKey) {
2889
3022
  e.preventDefault();
2890
- send(input);
3023
+ sendTyped(input);
2891
3024
  }
2892
3025
  },
2893
3026
  placeholder: phase === "draft" ? "Anything to change? Tell me here\u2026" : "Type your answer\u2026",
@@ -2917,7 +3050,7 @@ function AssistSheet({
2917
3050
  {
2918
3051
  type: "button",
2919
3052
  "data-gg-sheet-send": "",
2920
- onClick: () => send(input),
3053
+ onClick: () => sendTyped(input),
2921
3054
  disabled: busy || !input.trim(),
2922
3055
  "aria-label": "Send message",
2923
3056
  "data-gg-send-message": "",
@@ -2956,12 +3089,87 @@ function AssistSheet({
2956
3089
  children: validationError
2957
3090
  }
2958
3091
  ),
2959
- /* @__PURE__ */ jsx2(
3092
+ autoFileLeft !== null ? (
3093
+ // #402: "Yes, that's it" was the confirmation. The draft
3094
+ // files itself unless they stop it — the bar shows how
3095
+ // long is left, the button sends now.
3096
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: "8px", marginTop: "8px" }, children: [
3097
+ /* @__PURE__ */ jsxs2(
3098
+ "button",
3099
+ {
3100
+ type: "button",
3101
+ "data-gg-sheet-send": "",
3102
+ "data-gg-auto-file": "",
3103
+ onClick: () => sendDraft(),
3104
+ style: {
3105
+ flex: 1,
3106
+ minWidth: 0,
3107
+ position: "relative",
3108
+ overflow: "hidden",
3109
+ padding: "12px",
3110
+ borderRadius: "10px",
3111
+ border: "none",
3112
+ backgroundColor: t2.accent,
3113
+ color: t2.accentText,
3114
+ fontSize: "14px",
3115
+ fontWeight: 700,
3116
+ fontFamily: "inherit",
3117
+ cursor: "pointer"
3118
+ },
3119
+ children: [
3120
+ /* @__PURE__ */ jsx2(
3121
+ "span",
3122
+ {
3123
+ "aria-hidden": "true",
3124
+ style: {
3125
+ position: "absolute",
3126
+ inset: 0,
3127
+ backgroundColor: "rgba(255,255,255,0.22)",
3128
+ transformOrigin: "left",
3129
+ animation: `gg-autofile ${AUTO_FILE_SECONDS}s linear forwards`
3130
+ }
3131
+ }
3132
+ ),
3133
+ /* @__PURE__ */ jsxs2("span", { style: { position: "relative" }, children: [
3134
+ "Filing in ",
3135
+ autoFileLeft,
3136
+ "s\u2026"
3137
+ ] })
3138
+ ]
3139
+ }
3140
+ ),
3141
+ /* @__PURE__ */ jsx2(
3142
+ "button",
3143
+ {
3144
+ type: "button",
3145
+ "data-gg-edit-first": "",
3146
+ onClick: () => {
3147
+ var _a;
3148
+ stopAutoFile("edit first");
3149
+ (_a = draftRef.current) == null ? void 0 : _a.focus();
3150
+ },
3151
+ style: {
3152
+ flexShrink: 0,
3153
+ padding: "12px 14px",
3154
+ borderRadius: "10px",
3155
+ border: `1px solid ${t2.inputBorder}`,
3156
+ background: "transparent",
3157
+ color: t2.text,
3158
+ fontSize: "13px",
3159
+ fontWeight: 600,
3160
+ fontFamily: "inherit",
3161
+ cursor: "pointer"
3162
+ },
3163
+ children: "Edit first"
3164
+ }
3165
+ )
3166
+ ] })
3167
+ ) : /* @__PURE__ */ jsx2(
2960
3168
  "button",
2961
3169
  {
2962
3170
  type: "button",
2963
3171
  "data-gg-sheet-send": "",
2964
- onClick: sendDraft,
3172
+ onClick: () => sendDraft(),
2965
3173
  disabled: isSubmitting || !description.trim(),
2966
3174
  style: {
2967
3175
  width: "100%",
@@ -3937,9 +4145,11 @@ function ReportDialog({
3937
4145
  reporter,
3938
4146
  onClose,
3939
4147
  headerSlot,
3940
- reportGlitchgrabProblem
4148
+ reportGlitchgrabProblem,
4149
+ layout = "modal"
3941
4150
  }) {
3942
4151
  var _a;
4152
+ const sheet = layout === "sheet";
3943
4153
  const [isEnhancing, setIsEnhancing] = useState4(false);
3944
4154
  const [assistOpen, setAssistOpen] = useState4(false);
3945
4155
  const [assistNotice, setAssistNotice] = useState4(null);
@@ -4747,8 +4957,8 @@ function ReportDialog({
4747
4957
  // dialog's state IS the report, and unmounting would drop the
4748
4958
  // screenshots, attachments and step the reporter is on.
4749
4959
  display: sheetUp ? "none" : "flex",
4750
- alignItems: "center",
4751
- justifyContent: "center",
4960
+ alignItems: sheet ? "stretch" : "center",
4961
+ justifyContent: sheet ? "flex-end" : "center",
4752
4962
  backgroundColor: "rgba(0,0,0,0.5)",
4753
4963
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
4754
4964
  },
@@ -4768,14 +4978,15 @@ function ReportDialog({
4768
4978
  style: {
4769
4979
  position: "relative",
4770
4980
  zIndex: 2147483647,
4771
- width: "420px",
4772
- maxWidth: "calc(100% - 32px)",
4773
- maxHeight: "calc(100dvh - 32px)",
4981
+ width: sheet ? "440px" : "420px",
4982
+ maxWidth: sheet ? "100%" : "calc(100% - 32px)",
4983
+ maxHeight: sheet ? "100dvh" : "calc(100dvh - 32px)",
4984
+ ...sheet ? { height: "100dvh", borderLeft: `1px solid ${t2.inputBorder}` } : {},
4774
4985
  display: "flex",
4775
4986
  flexDirection: "column",
4776
4987
  backgroundColor: t2.bg,
4777
- borderRadius: "12px",
4778
- boxShadow: "0 20px 60px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.1)",
4988
+ borderRadius: sheet ? 0 : "12px",
4989
+ boxShadow: sheet ? "0 20px 60px rgba(0, 0, 0, 0.35)" : "0 20px 60px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.1)",
4779
4990
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
4780
4991
  overflow: "hidden",
4781
4992
  color: t2.text,
@@ -4809,7 +5020,7 @@ function ReportDialog({
4809
5020
  gap: "8px"
4810
5021
  },
4811
5022
  children: [
4812
- step > 1 && /* @__PURE__ */ jsx4(
5023
+ step > 1 && availableTypes.length > 1 && /* @__PURE__ */ jsx4(
4813
5024
  "button",
4814
5025
  {
4815
5026
  type: "button",
@@ -4927,7 +5138,7 @@ function ReportDialog({
4927
5138
  ]
4928
5139
  }
4929
5140
  ),
4930
- /* @__PURE__ */ jsx4(
5141
+ availableTypes.length > 1 && /* @__PURE__ */ jsx4(
4931
5142
  "div",
4932
5143
  {
4933
5144
  style: {
@@ -5203,6 +5414,7 @@ function ReportDialog({
5203
5414
  "button",
5204
5415
  {
5205
5416
  type: "button",
5417
+ disabled: availableTypes.length === 1,
5206
5418
  onClick: () => setStep(1),
5207
5419
  style: {
5208
5420
  display: "inline-flex",
@@ -5217,12 +5429,12 @@ function ReportDialog({
5217
5429
  fontSize: "11px",
5218
5430
  fontWeight: 600,
5219
5431
  fontFamily: "inherit",
5220
- cursor: "pointer"
5432
+ cursor: availableTypes.length === 1 ? "default" : "pointer"
5221
5433
  },
5222
5434
  children: [
5223
5435
  getTypeIcon(reportType, isRating ? "#f59e0b" : t2.accent, 13),
5224
5436
  getTypeLabel(reportType),
5225
- /* @__PURE__ */ jsx4("span", { style: { color: t2.textMuted, fontWeight: 400 }, children: "\xB7 change" })
5437
+ availableTypes.length > 1 && /* @__PURE__ */ jsx4("span", { style: { color: t2.textMuted, fontWeight: 400 }, children: "\xB7 change" })
5226
5438
  ]
5227
5439
  }
5228
5440
  ),