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.js CHANGED
@@ -73,6 +73,37 @@ var import_react6 = require("react");
73
73
  // src/error-boundary.tsx
74
74
  var React = __toESM(require("react"));
75
75
 
76
+ // ../report-ui/dist/assist-response.mjs
77
+ var ASSIST_OFFLINE_MESSAGE = "The assistant is unavailable \u2014 write your report below and send it as normal.";
78
+ function assistOffline() {
79
+ return { conversationId: null, question: null, report: null, degraded: ASSIST_OFFLINE_MESSAGE };
80
+ }
81
+ function toAssistTurnResult(ok, body) {
82
+ var _a, _b, _c, _d, _e, _f, _g, _h;
83
+ const envelope = body != null ? body : null;
84
+ if (!ok || !(envelope == null ? void 0 : envelope.success)) {
85
+ return {
86
+ ...assistOffline(),
87
+ // The server words why (cap, rate limit, switched off). An hourly limit
88
+ // keeps the assistant button for this report.
89
+ degraded: (_a = envelope == null ? void 0 : envelope.error) != null ? _a : ASSIST_OFFLINE_MESSAGE,
90
+ retryable: (envelope == null ? void 0 : envelope.retryable) === true
91
+ };
92
+ }
93
+ const d = (_b = envelope.data) != null ? _b : {};
94
+ return {
95
+ conversationId: (_c = d.conversationId) != null ? _c : null,
96
+ question: (_d = d.question) != null ? _d : null,
97
+ options: Array.isArray(d.options) ? d.options.filter((o) => typeof o === "string") : [],
98
+ report: (_e = d.report) != null ? _e : null,
99
+ duplicate: (_f = d.duplicate) != null ? _f : null,
100
+ related: (_g = d.related) != null ? _g : null,
101
+ solved: (_h = d.solved) != null ? _h : null,
102
+ aboutGlitchgrab: d.aboutGlitchgrab === true,
103
+ degraded: null
104
+ };
105
+ }
106
+
76
107
  // src/redact.ts
77
108
  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;
78
109
  var EMAIL_RE = /[\w.+-]+@[\w-]+\.[\w.-]+/g;
@@ -747,13 +778,7 @@ async function findSimilarIssues(description, token, baseUrl, identity) {
747
778
  }
748
779
  }
749
780
  async function assistTurn(params, token, baseUrl) {
750
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
751
- const offline = {
752
- conversationId: null,
753
- question: null,
754
- report: null,
755
- degraded: "The assistant is unavailable \u2014 write your report below and send it as normal."
756
- };
781
+ const offline = assistOffline();
757
782
  try {
758
783
  const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/ai/report-chat`;
759
784
  const response = await fetch(url, {
@@ -764,28 +789,7 @@ async function assistTurn(params, token, baseUrl) {
764
789
  },
765
790
  body: JSON.stringify(params)
766
791
  });
767
- const envelope = await response.json().catch(() => null);
768
- if (!response.ok || !(envelope == null ? void 0 : envelope.success)) {
769
- return {
770
- conversationId: null,
771
- question: null,
772
- report: null,
773
- degraded: (_a = envelope == null ? void 0 : envelope.error) != null ? _a : offline.degraded,
774
- // An hourly limit, not a cap: the dialog keeps the assistant button.
775
- retryable: (envelope == null ? void 0 : envelope.retryable) === true
776
- };
777
- }
778
- return {
779
- conversationId: (_c = (_b = envelope.data) == null ? void 0 : _b.conversationId) != null ? _c : null,
780
- question: (_e = (_d = envelope.data) == null ? void 0 : _d.question) != null ? _e : null,
781
- options: (_g = (_f = envelope.data) == null ? void 0 : _f.options) != null ? _g : [],
782
- report: (_i = (_h = envelope.data) == null ? void 0 : _h.report) != null ? _i : null,
783
- duplicate: (_k = (_j = envelope.data) == null ? void 0 : _j.duplicate) != null ? _k : null,
784
- related: (_m = (_l = envelope.data) == null ? void 0 : _l.related) != null ? _m : null,
785
- solved: (_o = (_n = envelope.data) == null ? void 0 : _n.solved) != null ? _o : null,
786
- aboutGlitchgrab: ((_p = envelope.data) == null ? void 0 : _p.aboutGlitchgrab) === true,
787
- degraded: null
788
- };
792
+ return toAssistTurnResult(response.ok, await response.json().catch(() => null));
789
793
  } catch (e) {
790
794
  return offline;
791
795
  }
@@ -1475,6 +1479,17 @@ var STARTERS = [
1475
1479
  "It did the wrong thing",
1476
1480
  "I wish it could\u2026"
1477
1481
  ];
1482
+ var CONFIRM_OPTION = "Yes, that's it";
1483
+ function isCheckBackQuestion(text) {
1484
+ const t2 = text.trim();
1485
+ 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);
1486
+ }
1487
+ function isAffirmativeReply(text) {
1488
+ 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(
1489
+ text.trim()
1490
+ );
1491
+ }
1492
+ var AUTO_FILE_SECONDS = 4;
1478
1493
  var NARROW_QUERY = "(max-width: 640px)";
1479
1494
  function useIsNarrow() {
1480
1495
  const [narrow, setNarrow] = (0, import_react3.useState)(false);
@@ -1589,12 +1604,15 @@ function AssistSheet({
1589
1604
  const [duplicate, setDuplicate] = (0, import_react3.useState)(null);
1590
1605
  const [glitchgrabOffer, setGlitchgrabOffer] = (0, import_react3.useState)(false);
1591
1606
  const [solved, setSolved] = (0, import_react3.useState)(null);
1607
+ const needsSeverity = (tile) => showSeverity && tile === "BUG" && !severity;
1592
1608
  const [phase, setPhase] = (0, import_react3.useState)(
1593
- !typePicked ? "type" : currentTile === "RATING" ? "rating" : "chat"
1609
+ () => !typePicked ? "type" : currentTile === "RATING" ? "rating" : needsSeverity(currentTile) ? "severity" : "chat"
1594
1610
  );
1611
+ const [autoFileLeft, setAutoFileLeft] = (0, import_react3.useState)(null);
1595
1612
  const scrollRef = (0, import_react3.useRef)(null);
1596
1613
  const draftRef = (0, import_react3.useRef)(null);
1597
1614
  const seededRef = (0, import_react3.useRef)(false);
1615
+ const seedPendingRef = (0, import_react3.useRef)(false);
1598
1616
  const eventsRef = (0, import_react3.useRef)([]);
1599
1617
  const aiDraftRef = (0, import_react3.useRef)(null);
1600
1618
  function logEvent(type, detail) {
@@ -1634,14 +1652,33 @@ function AssistSheet({
1634
1652
  var _a;
1635
1653
  if (phase === "draft") (_a = draftRef.current) == null ? void 0 : _a.focus();
1636
1654
  }, [phase]);
1655
+ (0, import_react3.useEffect)(() => {
1656
+ if (autoFileLeft === null) return;
1657
+ if (isSubmitting || submitted || phase !== "draft") {
1658
+ setAutoFileLeft(null);
1659
+ return;
1660
+ }
1661
+ if (autoFileLeft <= 0) {
1662
+ sendDraft(true);
1663
+ return;
1664
+ }
1665
+ const id = setTimeout(() => setAutoFileLeft((n) => n === null ? null : n - 1), 1e3);
1666
+ return () => clearTimeout(id);
1667
+ }, [autoFileLeft, isSubmitting, submitted, phase]);
1668
+ function stopAutoFile(reason) {
1669
+ if (autoFileLeft === null) return;
1670
+ setAutoFileLeft(null);
1671
+ logEvent("auto_file_stopped", reason);
1672
+ }
1637
1673
  function degradeKeepingEvents(events, message, retryable) {
1638
1674
  if (events) eventsRef.current = [...events, ...eventsRef.current];
1639
1675
  flushEvents();
1640
1676
  onDegrade(message, retryable);
1641
1677
  }
1642
- async function runTurn(history2) {
1678
+ async function runTurn(history2, autoFile = false) {
1643
1679
  var _a, _b;
1644
1680
  setBusy(true);
1681
+ setAutoFileLeft(null);
1645
1682
  let result;
1646
1683
  const events = takeEvents();
1647
1684
  try {
@@ -1701,6 +1738,9 @@ function AssistSheet({
1701
1738
  setMessages([...history2, { role: "assistant", content: result.report, kind: "report" }]);
1702
1739
  setOptions([]);
1703
1740
  setPhase("draft");
1741
+ if (autoFile && !attach && !(severityAsked && !severity)) {
1742
+ setAutoFileLeft(AUTO_FILE_SECONDS);
1743
+ }
1704
1744
  return;
1705
1745
  }
1706
1746
  if (result.question) {
@@ -1714,13 +1754,34 @@ function AssistSheet({
1714
1754
  if (seededRef.current) return;
1715
1755
  seededRef.current = true;
1716
1756
  if (!typePicked || currentTile === "RATING") return;
1757
+ if (phase === "severity") {
1758
+ seedPendingRef.current = true;
1759
+ return;
1760
+ }
1761
+ seedFromDescription();
1762
+ }, []);
1763
+ function seedFromDescription() {
1717
1764
  const seed = description.trim();
1718
1765
  if (!seed) return;
1719
1766
  const history2 = [{ role: "user", content: seed }];
1720
1767
  setMessages(history2);
1721
1768
  void runTurn(history2);
1722
- }, []);
1723
- function send(text) {
1769
+ }
1770
+ function answerSeverity(value) {
1771
+ onSeverityChange(value);
1772
+ setPhase("chat");
1773
+ if (seedPendingRef.current) {
1774
+ seedPendingRef.current = false;
1775
+ seedFromDescription();
1776
+ }
1777
+ }
1778
+ function sendTyped(text) {
1779
+ var _a;
1780
+ const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
1781
+ const checkingBack = options.includes(CONFIRM_OPTION) || (lastAssistant == null ? void 0 : lastAssistant.kind) !== "report" && isCheckBackQuestion((_a = lastAssistant == null ? void 0 : lastAssistant.content) != null ? _a : "");
1782
+ send(text, checkingBack && isAffirmativeReply(text));
1783
+ }
1784
+ function send(text, autoFile = false) {
1724
1785
  const value = text.trim();
1725
1786
  if (!value || busy) return;
1726
1787
  setOptions([]);
@@ -1735,11 +1796,11 @@ function AssistSheet({
1735
1796
  const history2 = [...base, { role: "user", content: value }];
1736
1797
  setMessages(history2);
1737
1798
  setInput("");
1738
- void runTurn(history2);
1799
+ void runTurn(history2, autoFile);
1739
1800
  }
1740
1801
  function pickType(tile) {
1741
1802
  onPickType(tile);
1742
- setPhase(tile === "RATING" ? "rating" : "chat");
1803
+ setPhase(tile === "RATING" ? "rating" : needsSeverity(tile) ? "severity" : "chat");
1743
1804
  }
1744
1805
  function withCurrentDraft(history2) {
1745
1806
  const current = description.trim();
@@ -1752,9 +1813,10 @@ function AssistSheet({
1752
1813
  }
1753
1814
  return next;
1754
1815
  }
1755
- function sendDraft() {
1816
+ function sendDraft(auto = false) {
1817
+ setAutoFileLeft(null);
1756
1818
  const edited = aiDraftRef.current !== null && description.trim() !== aiDraftRef.current.trim();
1757
- logEvent("draft_sent", edited ? "edited" : "as drafted");
1819
+ logEvent("draft_sent", auto ? "auto-filed" : edited ? "edited" : "as drafted");
1758
1820
  flushEvents();
1759
1821
  onSend();
1760
1822
  }
@@ -1814,6 +1876,7 @@ function AssistSheet({
1814
1876
  @keyframes gg-sheet-in{from{transform:translateX(24px);opacity:0}to{transform:translateX(0);opacity:1}}
1815
1877
  @keyframes gg-sheet-up{from{transform:translateY(24px);opacity:0}to{transform:translateY(0);opacity:1}}
1816
1878
  @keyframes gg-sheet-fade{from{opacity:0}to{opacity:1}}
1879
+ @keyframes gg-autofile{from{transform:scaleX(0)}to{transform:scaleX(1)}}
1817
1880
  @keyframes gg-dot{0%,80%,100%{transform:translateY(0);opacity:.4}40%{transform:translateY(-3px);opacity:1}}
1818
1881
  [data-gg-sheet-send]:focus-visible,[data-gg-chip]:focus-visible,[data-gg-assist-remove]:focus-visible{outline:2px solid currentColor;outline-offset:2px}
1819
1882
  ` }),
@@ -1908,6 +1971,7 @@ function AssistSheet({
1908
1971
  ),
1909
1972
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { fontSize: "11.5px", color: t2.textMuted, marginTop: "3px" }, children: [
1910
1973
  phase === "type" ? null : reportTypeLabel,
1974
+ phase !== "type" && severityAsked && severity ? ` \xB7 ${SEVERITY_LABELS[severity]}` : null,
1911
1975
  projectSlot ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { marginLeft: "6px" }, children: projectSlot }) : null
1912
1976
  ] })
1913
1977
  ] }),
@@ -2026,6 +2090,62 @@ function AssistSheet({
2026
2090
  }
2027
2091
  )
2028
2092
  ] }),
2093
+ phase === "severity" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
2094
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
2095
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Avatar, { who: "assistant", theme: t2 }),
2096
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2097
+ "div",
2098
+ {
2099
+ style: {
2100
+ padding: "9px 12px",
2101
+ borderRadius: "12px 12px 12px 4px",
2102
+ backgroundColor: t2.bgSecondary,
2103
+ fontSize: "13px",
2104
+ lineHeight: 1.55,
2105
+ maxWidth: "88%"
2106
+ },
2107
+ children: "How bad is it?"
2108
+ }
2109
+ )
2110
+ ] }),
2111
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2112
+ "div",
2113
+ {
2114
+ role: "radiogroup",
2115
+ "aria-label": "Severity",
2116
+ style: {
2117
+ display: "flex",
2118
+ flexWrap: "wrap",
2119
+ gap: "6px",
2120
+ paddingLeft: "32px"
2121
+ },
2122
+ children: SEVERITY_LEVELS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2123
+ "button",
2124
+ {
2125
+ type: "button",
2126
+ role: "radio",
2127
+ "aria-checked": severity === s,
2128
+ "data-gg-chip": "",
2129
+ "data-gg-severity": s,
2130
+ onClick: () => answerSeverity(s),
2131
+ style: {
2132
+ border: `1px solid ${t2.inputBorder}`,
2133
+ background: "transparent",
2134
+ color: t2.textMuted,
2135
+ borderRadius: "999px",
2136
+ // Same as the picker and option chips — one chip size.
2137
+ padding: "8px 12px",
2138
+ fontSize: "12px",
2139
+ fontFamily: "inherit",
2140
+ cursor: "pointer"
2141
+ },
2142
+ children: SEVERITY_LABELS[s]
2143
+ },
2144
+ s
2145
+ ))
2146
+ }
2147
+ )
2148
+ ] }),
2029
2149
  phase === "rating" && !submitted && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
2030
2150
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
2031
2151
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Avatar, { who: "assistant", theme: t2 }),
@@ -2214,7 +2334,11 @@ function AssistSheet({
2214
2334
  color: t2.text,
2215
2335
  border: live ? "none" : `1px solid ${t2.accent}66`
2216
2336
  },
2217
- children: live ? "Here's your report \u2014 have a read." : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2337
+ children: live ? (
2338
+ // #402: "have a read" beside a countdown reads as a
2339
+ // contradiction. Say what is about to happen instead.
2340
+ 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."
2341
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2218
2342
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2219
2343
  "span",
2220
2344
  {
@@ -2381,7 +2505,7 @@ function AssistSheet({
2381
2505
  "data-gg-option": "",
2382
2506
  onClick: () => {
2383
2507
  logEvent("option_tap", option);
2384
- send(option);
2508
+ send(option, option === CONFIRM_OPTION);
2385
2509
  },
2386
2510
  style: {
2387
2511
  border: `1px solid ${t2.inputBorder}`,
@@ -2468,7 +2592,10 @@ function AssistSheet({
2468
2592
  {
2469
2593
  ref: draftRef,
2470
2594
  value: description,
2471
- onChange: (e) => onDescriptionChange(e.target.value),
2595
+ onChange: (e) => {
2596
+ stopAutoFile("edited the draft");
2597
+ onDescriptionChange(e.target.value);
2598
+ },
2472
2599
  rows: 6,
2473
2600
  style: {
2474
2601
  width: "100%",
@@ -2487,7 +2614,7 @@ function AssistSheet({
2487
2614
  }
2488
2615
  }
2489
2616
  ),
2490
- severityAsked && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
2617
+ severityAsked && autoFileLeft === null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
2491
2618
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2492
2619
  "span",
2493
2620
  {
@@ -2609,7 +2736,7 @@ function AssistSheet({
2609
2736
  )
2610
2737
  }
2611
2738
  ),
2612
- !submitted && phase !== "type" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2739
+ !submitted && phase !== "type" && phase !== "severity" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2613
2740
  "div",
2614
2741
  {
2615
2742
  style: {
@@ -2716,6 +2843,7 @@ function AssistSheet({
2716
2843
  "aria-label": `Remove image ${i + 1}`,
2717
2844
  title: "Remove this image",
2718
2845
  onClick: () => {
2846
+ stopAutoFile("changed attachments");
2719
2847
  logEvent(
2720
2848
  "image_removed",
2721
2849
  pageShot && shot === pageShot ? "page capture" : "attached"
@@ -2826,6 +2954,7 @@ function AssistSheet({
2826
2954
  "aria-label": `Remove file ${file.name}`,
2827
2955
  title: "Remove this file",
2828
2956
  onClick: () => {
2957
+ stopAutoFile("changed attachments");
2829
2958
  logEvent("file_removed", read ? "read" : "not read");
2830
2959
  onRemoveFile(i);
2831
2960
  },
@@ -2908,6 +3037,7 @@ function AssistSheet({
2908
3037
  var _a;
2909
3038
  const picked = Array.from((_a = e.target.files) != null ? _a : []);
2910
3039
  if (picked.length) {
3040
+ stopAutoFile("changed attachments");
2911
3041
  const images = picked.filter((f) => f.type.startsWith("image/")).length;
2912
3042
  if (images) logEvent("image_added", String(images));
2913
3043
  if (picked.length > images) {
@@ -2937,11 +3067,14 @@ function AssistSheet({
2937
3067
  "textarea",
2938
3068
  {
2939
3069
  value: input,
2940
- onChange: (e) => setInput(e.target.value),
3070
+ onChange: (e) => {
3071
+ stopAutoFile("typing a change");
3072
+ setInput(e.target.value);
3073
+ },
2941
3074
  onKeyDown: (e) => {
2942
3075
  if (e.key === "Enter" && !e.shiftKey) {
2943
3076
  e.preventDefault();
2944
- send(input);
3077
+ sendTyped(input);
2945
3078
  }
2946
3079
  },
2947
3080
  placeholder: phase === "draft" ? "Anything to change? Tell me here\u2026" : "Type your answer\u2026",
@@ -2971,7 +3104,7 @@ function AssistSheet({
2971
3104
  {
2972
3105
  type: "button",
2973
3106
  "data-gg-sheet-send": "",
2974
- onClick: () => send(input),
3107
+ onClick: () => sendTyped(input),
2975
3108
  disabled: busy || !input.trim(),
2976
3109
  "aria-label": "Send message",
2977
3110
  "data-gg-send-message": "",
@@ -3010,12 +3143,87 @@ function AssistSheet({
3010
3143
  children: validationError
3011
3144
  }
3012
3145
  ),
3013
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3146
+ autoFileLeft !== null ? (
3147
+ // #402: "Yes, that's it" was the confirmation. The draft
3148
+ // files itself unless they stop it — the bar shows how
3149
+ // long is left, the button sends now.
3150
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: "8px", marginTop: "8px" }, children: [
3151
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3152
+ "button",
3153
+ {
3154
+ type: "button",
3155
+ "data-gg-sheet-send": "",
3156
+ "data-gg-auto-file": "",
3157
+ onClick: () => sendDraft(),
3158
+ style: {
3159
+ flex: 1,
3160
+ minWidth: 0,
3161
+ position: "relative",
3162
+ overflow: "hidden",
3163
+ padding: "12px",
3164
+ borderRadius: "10px",
3165
+ border: "none",
3166
+ backgroundColor: t2.accent,
3167
+ color: t2.accentText,
3168
+ fontSize: "14px",
3169
+ fontWeight: 700,
3170
+ fontFamily: "inherit",
3171
+ cursor: "pointer"
3172
+ },
3173
+ children: [
3174
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3175
+ "span",
3176
+ {
3177
+ "aria-hidden": "true",
3178
+ style: {
3179
+ position: "absolute",
3180
+ inset: 0,
3181
+ backgroundColor: "rgba(255,255,255,0.22)",
3182
+ transformOrigin: "left",
3183
+ animation: `gg-autofile ${AUTO_FILE_SECONDS}s linear forwards`
3184
+ }
3185
+ }
3186
+ ),
3187
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { position: "relative" }, children: [
3188
+ "Filing in ",
3189
+ autoFileLeft,
3190
+ "s\u2026"
3191
+ ] })
3192
+ ]
3193
+ }
3194
+ ),
3195
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3196
+ "button",
3197
+ {
3198
+ type: "button",
3199
+ "data-gg-edit-first": "",
3200
+ onClick: () => {
3201
+ var _a;
3202
+ stopAutoFile("edit first");
3203
+ (_a = draftRef.current) == null ? void 0 : _a.focus();
3204
+ },
3205
+ style: {
3206
+ flexShrink: 0,
3207
+ padding: "12px 14px",
3208
+ borderRadius: "10px",
3209
+ border: `1px solid ${t2.inputBorder}`,
3210
+ background: "transparent",
3211
+ color: t2.text,
3212
+ fontSize: "13px",
3213
+ fontWeight: 600,
3214
+ fontFamily: "inherit",
3215
+ cursor: "pointer"
3216
+ },
3217
+ children: "Edit first"
3218
+ }
3219
+ )
3220
+ ] })
3221
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3014
3222
  "button",
3015
3223
  {
3016
3224
  type: "button",
3017
3225
  "data-gg-sheet-send": "",
3018
- onClick: sendDraft,
3226
+ onClick: () => sendDraft(),
3019
3227
  disabled: isSubmitting || !description.trim(),
3020
3228
  style: {
3021
3229
  width: "100%",
@@ -3991,9 +4199,11 @@ function ReportDialog({
3991
4199
  reporter,
3992
4200
  onClose,
3993
4201
  headerSlot,
3994
- reportGlitchgrabProblem
4202
+ reportGlitchgrabProblem,
4203
+ layout = "modal"
3995
4204
  }) {
3996
4205
  var _a;
4206
+ const sheet = layout === "sheet";
3997
4207
  const [isEnhancing, setIsEnhancing] = (0, import_react.useState)(false);
3998
4208
  const [assistOpen, setAssistOpen] = (0, import_react.useState)(false);
3999
4209
  const [assistNotice, setAssistNotice] = (0, import_react.useState)(null);
@@ -4801,8 +5011,8 @@ function ReportDialog({
4801
5011
  // dialog's state IS the report, and unmounting would drop the
4802
5012
  // screenshots, attachments and step the reporter is on.
4803
5013
  display: sheetUp ? "none" : "flex",
4804
- alignItems: "center",
4805
- justifyContent: "center",
5014
+ alignItems: sheet ? "stretch" : "center",
5015
+ justifyContent: sheet ? "flex-end" : "center",
4806
5016
  backgroundColor: "rgba(0,0,0,0.5)",
4807
5017
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
4808
5018
  },
@@ -4822,14 +5032,15 @@ function ReportDialog({
4822
5032
  style: {
4823
5033
  position: "relative",
4824
5034
  zIndex: 2147483647,
4825
- width: "420px",
4826
- maxWidth: "calc(100% - 32px)",
4827
- maxHeight: "calc(100dvh - 32px)",
5035
+ width: sheet ? "440px" : "420px",
5036
+ maxWidth: sheet ? "100%" : "calc(100% - 32px)",
5037
+ maxHeight: sheet ? "100dvh" : "calc(100dvh - 32px)",
5038
+ ...sheet ? { height: "100dvh", borderLeft: `1px solid ${t2.inputBorder}` } : {},
4828
5039
  display: "flex",
4829
5040
  flexDirection: "column",
4830
5041
  backgroundColor: t2.bg,
4831
- borderRadius: "12px",
4832
- boxShadow: "0 20px 60px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.1)",
5042
+ borderRadius: sheet ? 0 : "12px",
5043
+ 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)",
4833
5044
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
4834
5045
  overflow: "hidden",
4835
5046
  color: t2.text,
@@ -4863,7 +5074,7 @@ function ReportDialog({
4863
5074
  gap: "8px"
4864
5075
  },
4865
5076
  children: [
4866
- step > 1 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
5077
+ step > 1 && availableTypes.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4867
5078
  "button",
4868
5079
  {
4869
5080
  type: "button",
@@ -4981,7 +5192,7 @@ function ReportDialog({
4981
5192
  ]
4982
5193
  }
4983
5194
  ),
4984
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
5195
+ availableTypes.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4985
5196
  "div",
4986
5197
  {
4987
5198
  style: {
@@ -5257,6 +5468,7 @@ function ReportDialog({
5257
5468
  "button",
5258
5469
  {
5259
5470
  type: "button",
5471
+ disabled: availableTypes.length === 1,
5260
5472
  onClick: () => setStep(1),
5261
5473
  style: {
5262
5474
  display: "inline-flex",
@@ -5271,12 +5483,12 @@ function ReportDialog({
5271
5483
  fontSize: "11px",
5272
5484
  fontWeight: 600,
5273
5485
  fontFamily: "inherit",
5274
- cursor: "pointer"
5486
+ cursor: availableTypes.length === 1 ? "default" : "pointer"
5275
5487
  },
5276
5488
  children: [
5277
5489
  getTypeIcon(reportType, isRating ? "#f59e0b" : t2.accent, 13),
5278
5490
  getTypeLabel(reportType),
5279
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: t2.textMuted, fontWeight: 400 }, children: "\xB7 change" })
5491
+ availableTypes.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: t2.textMuted, fontWeight: 400 }, children: "\xB7 change" })
5280
5492
  ]
5281
5493
  }
5282
5494
  ),