glitchgrab 1.58.2 → 1.58.3

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
@@ -178,7 +178,7 @@ function argsToString(args) {
178
178
  function previewJson(value, max) {
179
179
  let out = "";
180
180
  const path = /* @__PURE__ */ new WeakSet();
181
- const write = (input, inArray) => {
181
+ const write2 = (input, inArray) => {
182
182
  if (out.length > max) return;
183
183
  let v = input;
184
184
  if (v !== null && typeof v === "object" && typeof v.toJSON === "function") {
@@ -215,7 +215,7 @@ function previewJson(value, max) {
215
215
  out += "[";
216
216
  for (let i = 0; i < obj.length && out.length <= max; i++) {
217
217
  if (i > 0) out += ",";
218
- write(obj[i], true);
218
+ write2(obj[i], true);
219
219
  }
220
220
  out += "]";
221
221
  } else {
@@ -228,13 +228,13 @@ function previewJson(value, max) {
228
228
  if (child === void 0 || typeof child === "function" || typeof child === "symbol") continue;
229
229
  out += `${first ? "" : ","}${JSON.stringify(key)}:`;
230
230
  first = false;
231
- write(child, false);
231
+ write2(child, false);
232
232
  }
233
233
  out += "}";
234
234
  }
235
235
  path.delete(obj);
236
236
  };
237
- write(value, false);
237
+ write2(value, false);
238
238
  return out;
239
239
  }
240
240
  async function readErrorBody(response, url) {
@@ -895,6 +895,292 @@ function clearDedupCache() {
895
895
  seen.clear();
896
896
  }
897
897
 
898
+ // src/hydration.ts
899
+ var HYDRATION_CODE_RE = /Minified React error #(418|419|422|423|425)\b/;
900
+ var HYDRATION_DEV_RE = /Hydration failed|hydration mismatch|Text content does not match|did not match\. Server|There was an error while hydrating|error occurred during hydration|server rendered HTML didn't match/i;
901
+ var BAILOUT_DIGEST = "BAILOUT_TO_CLIENT_SIDE_RENDERING";
902
+ var DOM_SNIPPET_MAX = 3e3;
903
+ var STACK_FRAMES_MAX = 40;
904
+ function messageOf(error) {
905
+ if (error && typeof error === "object" && typeof error.message === "string") {
906
+ return error.message;
907
+ }
908
+ return typeof error === "string" ? error : "";
909
+ }
910
+ function hydrationCode(message) {
911
+ if (!message) return null;
912
+ const m = HYDRATION_CODE_RE.exec(message);
913
+ if (m) return m[1];
914
+ return HYDRATION_DEV_RE.test(message) ? "dev" : null;
915
+ }
916
+ function isHydrationError(error) {
917
+ if (hydrationCode(messageOf(error))) return true;
918
+ const cause = error && typeof error === "object" ? error.cause : void 0;
919
+ return cause !== void 0 && cause !== error && hydrationCode(messageOf(cause)) !== null;
920
+ }
921
+ function isBailout(error) {
922
+ const e = error;
923
+ if (!e || typeof e !== "object") return false;
924
+ if (e.digest === BAILOUT_DIGEST) return true;
925
+ const c = e.cause;
926
+ return !!c && typeof c === "object" && c.digest === BAILOUT_DIGEST;
927
+ }
928
+ function componentStackNames(stack) {
929
+ if (!stack) return [];
930
+ const names = [];
931
+ for (const raw of stack.split("\n")) {
932
+ const line = raw.trim();
933
+ if (!line) continue;
934
+ let name = "";
935
+ if (line.startsWith("at ")) {
936
+ name = line.slice(3).split(" (")[0].trim();
937
+ } else if (line.includes("@")) {
938
+ name = line.slice(0, line.indexOf("@")).trim();
939
+ } else {
940
+ name = line;
941
+ }
942
+ if (name) names.push(name);
943
+ if (names.length >= STACK_FRAMES_MAX) break;
944
+ }
945
+ return names;
946
+ }
947
+ function componentStackKey(stack) {
948
+ return componentStackNames(stack).join(">");
949
+ }
950
+ var observer = null;
951
+ var observerStarted = false;
952
+ var observerStopAt = 0;
953
+ var OBSERVE_MS = 3e4;
954
+ function startObserver() {
955
+ if (observerStarted || typeof MutationObserver === "undefined" || typeof document === "undefined") {
956
+ return;
957
+ }
958
+ observerStarted = true;
959
+ try {
960
+ observer = new MutationObserver(() => {
961
+ if (Date.now() > observerStopAt) stopObserver();
962
+ });
963
+ observer.observe(document, { childList: true, subtree: true });
964
+ observerStopAt = Date.now() + OBSERVE_MS;
965
+ setTimeout(stopObserver, OBSERVE_MS);
966
+ } catch (e) {
967
+ observer = null;
968
+ }
969
+ }
970
+ function stopObserver() {
971
+ try {
972
+ observer == null ? void 0 : observer.disconnect();
973
+ } catch (e) {
974
+ }
975
+ observer = null;
976
+ }
977
+ function describeNode(node) {
978
+ if (!node) return "";
979
+ if (node.nodeType === 9) return "#document";
980
+ const el = node;
981
+ if (!el.tagName) return node.nodeName;
982
+ const id = el.id ? `#${el.id}` : "";
983
+ const cls = typeof el.className === "string" && el.className.trim() ? "." + el.className.trim().split(/\s+/).slice(0, 3).join(".") : "";
984
+ return `${el.tagName.toLowerCase()}${id}${cls}`;
985
+ }
986
+ function pathOf(node) {
987
+ const parts = [];
988
+ let cur = node;
989
+ while (cur && parts.length < 6) {
990
+ parts.unshift(describeNode(cur));
991
+ if (cur.nodeType === 9) break;
992
+ cur = cur.parentNode;
993
+ }
994
+ return parts.join(" > ");
995
+ }
996
+ var NON_CONTENT_TAGS = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "LINK", "META", "NOSCRIPT", "TEMPLATE", "TITLE", "HEAD"]);
997
+ function isInHead(node) {
998
+ for (let cur = node; cur; cur = cur.parentNode) {
999
+ if (cur.nodeName === "HEAD") return true;
1000
+ if (cur.nodeName === "BODY") return false;
1001
+ }
1002
+ return false;
1003
+ }
1004
+ var GSTIN_RE = /\b\d{2}[A-Z]{5}\d{4}[A-Z][A-Z0-9]Z[A-Z0-9]\b/g;
1005
+ var PAN_RE = /\b[A-Z]{5}\d{4}[A-Z]\b/g;
1006
+ function scrubHtml(html) {
1007
+ return redactString(
1008
+ html.replace(/\svalue="[^"]*"/gi, ' value="[REDACTED]"').replace(GSTIN_RE, "[REDACTED_GSTIN]").replace(PAN_RE, "[REDACTED_PAN]").replace(/\d{6,}/g, (m) => "#".repeat(Math.min(m.length, 6)))
1009
+ );
1010
+ }
1011
+ function takeRemovedServerHtml() {
1012
+ var _a, _b;
1013
+ if (!observer) return { html: "", where: "" };
1014
+ try {
1015
+ const records = observer.takeRecords();
1016
+ let html = "";
1017
+ let where = "";
1018
+ for (const rec of records) {
1019
+ if (rec.type !== "childList" || rec.removedNodes.length === 0) continue;
1020
+ if (isInHead(rec.target)) continue;
1021
+ if (!where) where = pathOf(rec.target);
1022
+ for (const node of Array.from(rec.removedNodes)) {
1023
+ if (node.nodeType === 1 && NON_CONTENT_TAGS.has(node.tagName)) continue;
1024
+ if (node.nodeType === 3 && !((_a = node.textContent) != null ? _a : "").trim()) continue;
1025
+ const piece = node.nodeType === 1 ? node.outerHTML : node.nodeType === 3 ? `#text "${(_b = node.textContent) != null ? _b : ""}"` : "";
1026
+ if (!piece) continue;
1027
+ html += (html ? "\n" : "") + piece;
1028
+ if (html.length >= DOM_SNIPPET_MAX) break;
1029
+ }
1030
+ if (html.length >= DOM_SNIPPET_MAX) break;
1031
+ }
1032
+ const scrubbed = scrubHtml(html);
1033
+ return {
1034
+ html: scrubbed.length > DOM_SNIPPET_MAX ? `${scrubbed.slice(0, DOM_SNIPPET_MAX)}\u2026[truncated]` : scrubbed,
1035
+ where
1036
+ };
1037
+ } catch (e) {
1038
+ return { html: "", where: "" };
1039
+ }
1040
+ }
1041
+ function detectForeignNodes(doc = document) {
1042
+ var _a;
1043
+ const found = [];
1044
+ try {
1045
+ const html = doc.documentElement;
1046
+ if ((html == null ? void 0 : html.classList.contains("translated-ltr")) || (html == null ? void 0 : html.classList.contains("translated-rtl"))) {
1047
+ found.push("Google Translate is active (html.translated-*)");
1048
+ }
1049
+ const fonts = doc.getElementsByTagName("font").length;
1050
+ if (fonts > 0) found.push(`<font> \xD7 ${fonts} (Google Translate or a translation extension)`);
1051
+ const detectors = doc.querySelectorAll("a[x-apple-data-detectors]").length;
1052
+ if (detectors > 0) {
1053
+ found.push(`<a x-apple-data-detectors> \xD7 ${detectors} (iOS format detection turned text into links)`);
1054
+ }
1055
+ if (doc.querySelector("[data-gr-ext-installed],[data-new-gr-c-s-check-loaded],grammarly-desktop-integration")) {
1056
+ found.push("Grammarly extension attributes");
1057
+ }
1058
+ if (doc.querySelector("[data-lt-installed]")) found.push("LanguageTool extension attributes");
1059
+ if ((_a = doc.body) == null ? void 0 : _a.hasAttribute("cz-shortcut-listen")) found.push("ColorZilla extension attribute on <body>");
1060
+ const custom = /* @__PURE__ */ new Set();
1061
+ for (const parent of [html, doc.body]) {
1062
+ if (!parent) continue;
1063
+ for (const child of Array.from(parent.children)) {
1064
+ const tag = child.tagName.toLowerCase();
1065
+ if (tag.includes("-")) custom.add(tag);
1066
+ }
1067
+ }
1068
+ if (custom.size > 0) {
1069
+ found.push(`custom elements at top level: ${Array.from(custom).slice(0, 5).join(", ")} (often an extension)`);
1070
+ }
1071
+ } catch (e) {
1072
+ }
1073
+ return found;
1074
+ }
1075
+ function hydrationTiming() {
1076
+ var _a, _b, _c, _d;
1077
+ try {
1078
+ if (typeof performance === "undefined") return "";
1079
+ const at2 = Math.round(performance.now());
1080
+ const parts = [`error at ${at2}ms after navigation start`];
1081
+ const nav = (_b = (_a = performance.getEntriesByType) == null ? void 0 : _a.call(performance, "navigation")) == null ? void 0 : _b[0];
1082
+ if (nav == null ? void 0 : nav.domContentLoadedEventEnd) {
1083
+ parts.push(`DOMContentLoaded ${Math.round(nav.domContentLoadedEventEnd)}ms`);
1084
+ }
1085
+ const fetches = ((_d = (_c = performance.getEntriesByType) == null ? void 0 : _c.call(performance, "resource")) != null ? _d : []).filter((e) => {
1086
+ const t = e.initiatorType;
1087
+ return t === "fetch" || t === "xmlhttprequest";
1088
+ }).sort((a, b) => a.responseEnd - b.responseEnd).slice(0, 8).map((e) => {
1089
+ let path = e.name;
1090
+ try {
1091
+ const u = new URL(e.name);
1092
+ path = u.origin === window.location.origin ? u.pathname : `${u.host}${u.pathname}`;
1093
+ } catch (e2) {
1094
+ }
1095
+ return `${path} ${Math.round(e.responseEnd)}ms`;
1096
+ });
1097
+ if (fetches.length > 0) parts.push(`fetches finished before it: ${fetches.join(", ")}`);
1098
+ else parts.push("no fetch had finished yet");
1099
+ return parts.join("; ");
1100
+ } catch (e) {
1101
+ return "";
1102
+ }
1103
+ }
1104
+ function hydrationMetadata(error, extra) {
1105
+ var _a, _b, _c;
1106
+ const code = (_a = hydrationCode(messageOf(error))) != null ? _a : hydrationCode(messageOf(error == null ? void 0 : error.cause));
1107
+ const meta = { errorKind: "hydration" };
1108
+ if (code) meta.hydrationCode = code;
1109
+ const cause = error == null ? void 0 : error.cause;
1110
+ if (cause && cause !== error && messageOf(cause)) {
1111
+ meta.hydrationCause = messageOf(cause).slice(0, 500);
1112
+ }
1113
+ if ((_b = extra == null ? void 0 : extra.removed) == null ? void 0 : _b.html) meta.hydrationServerHtml = extra.removed.html;
1114
+ if ((_c = extra == null ? void 0 : extra.removed) == null ? void 0 : _c.where) meta.hydrationReplacedIn = extra.removed.where;
1115
+ const foreign = detectForeignNodes();
1116
+ if (foreign.length > 0) meta.hydrationForeignNodes = foreign.join("\n");
1117
+ const timing = hydrationTiming();
1118
+ if (timing) meta.hydrationTiming = timing;
1119
+ return meta;
1120
+ }
1121
+ var handled = /* @__PURE__ */ new WeakSet();
1122
+ function isHandledHydrationError(error) {
1123
+ return !!error && typeof error === "object" && handled.has(error);
1124
+ }
1125
+ function markHandled(error) {
1126
+ if (error && typeof error === "object") {
1127
+ handled.add(error);
1128
+ const cause = error.cause;
1129
+ if (cause && typeof cause === "object") handled.add(cause);
1130
+ }
1131
+ }
1132
+ var reporter = null;
1133
+ var PATCHED = "__glitchgrabHydrationPatched";
1134
+ function findFiberRoot(container) {
1135
+ if (!container || typeof container !== "object") return null;
1136
+ try {
1137
+ const key = Object.keys(container).find((k) => k.startsWith("__reactContainer$"));
1138
+ if (!key) return null;
1139
+ const hostRoot = container[key];
1140
+ const root = hostRoot == null ? void 0 : hostRoot.stateNode;
1141
+ return root && typeof root === "object" ? root : null;
1142
+ } catch (e) {
1143
+ return null;
1144
+ }
1145
+ }
1146
+ function patchRoot(root) {
1147
+ if (root[PATCHED]) return true;
1148
+ const original = root.onRecoverableError;
1149
+ if (typeof original !== "function") return false;
1150
+ root.onRecoverableError = function glitchgrabOnRecoverableError(error, info) {
1151
+ var _a;
1152
+ try {
1153
+ if (reporter && isHydrationError(error) && !isBailout(error)) {
1154
+ const removed = takeRemovedServerHtml();
1155
+ markHandled(error);
1156
+ reporter(error, (_a = info == null ? void 0 : info.componentStack) != null ? _a : "", hydrationMetadata(error, { removed }));
1157
+ }
1158
+ } catch (e) {
1159
+ }
1160
+ return original.call(this, error, info);
1161
+ };
1162
+ root[PATCHED] = true;
1163
+ return true;
1164
+ }
1165
+ function patchKnownRoots() {
1166
+ if (typeof document === "undefined") return false;
1167
+ let patched = false;
1168
+ for (const container of [document, document.getElementById("__next"), document.getElementById("root")]) {
1169
+ const root = findFiberRoot(container);
1170
+ if (root && patchRoot(root)) patched = true;
1171
+ }
1172
+ return patched;
1173
+ }
1174
+ function installHydrationCapture(report) {
1175
+ try {
1176
+ if (typeof window === "undefined") return;
1177
+ reporter = report;
1178
+ startObserver();
1179
+ patchKnownRoots();
1180
+ } catch (e) {
1181
+ }
1182
+ }
1183
+
898
1184
  // src/capture.ts
899
1185
  var config = null;
900
1186
  function registerCaptureConfig(next) {
@@ -914,7 +1200,7 @@ function sessionMetadata(session) {
914
1200
  ...(session == null ? void 0 : session.phone) ? { sessionUserPhone: String(session.phone) } : {}
915
1201
  };
916
1202
  }
917
- function buildCapturedErrorPayload(error, options, cfg, capturedBy = "captureError") {
1203
+ function buildCapturedErrorPayload(error, options, cfg, capturedBy = "captureError", dedupKey) {
918
1204
  var _a, _b, _c;
919
1205
  const err = error instanceof Error ? error : void 0;
920
1206
  const errorMessage = err ? err.message : String(error);
@@ -926,7 +1212,7 @@ function buildCapturedErrorPayload(error, options, cfg, capturedBy = "captureErr
926
1212
  const sig = computeSignature({
927
1213
  errorMessage,
928
1214
  pageUrl: context.url,
929
- errorStack: err == null ? void 0 : err.stack,
1215
+ errorStack: dedupKey ? `at ${dedupKey}` : err == null ? void 0 : err.stack,
930
1216
  digest
931
1217
  });
932
1218
  if (shouldSkipDuplicate(sig)) return null;
@@ -966,6 +1252,25 @@ function captureError(error, options) {
966
1252
  } catch (e) {
967
1253
  }
968
1254
  }
1255
+ function reportHydrationError(error, componentStack, metadata) {
1256
+ try {
1257
+ const cfg = config;
1258
+ if (!(cfg == null ? void 0 : cfg.token)) return;
1259
+ const payload = buildCapturedErrorPayload(
1260
+ error,
1261
+ { componentStack: componentStack || void 0, metadata },
1262
+ cfg,
1263
+ "onRecoverableError",
1264
+ componentStackKey(componentStack) || void 0
1265
+ );
1266
+ if (!payload) return;
1267
+ void sendReport(payload, cfg.baseUrl).then((result) => {
1268
+ if (result && cfg.onReportSent) cfg.onReportSent(result);
1269
+ });
1270
+ if (cfg.onError && error instanceof Error) cfg.onError(error);
1271
+ } catch (e) {
1272
+ }
1273
+ }
969
1274
 
970
1275
  // src/error-boundary.tsx
971
1276
  var GlitchgrabErrorBoundary = class extends React.Component {
@@ -1892,6 +2197,79 @@ function renderBlocks(lines, theme, key) {
1892
2197
  function ChatMarkdown({ text, theme }) {
1893
2198
  return /* @__PURE__ */ jsx2("div", { style: STACK, children: renderBlocks(text.replace(/\r\n?/g, "\n").split("\n"), theme, "md") });
1894
2199
  }
2200
+ var MAX_SAVED_CHATS = 20;
2201
+ var SAVED_CHAT_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
2202
+ var TITLE_CHARS = 80;
2203
+ var PREFIX = "glitchgrab:ai-chats:";
2204
+ function chatHistoryKey(...parts) {
2205
+ const input = parts.map((p) => p != null ? p : "").join("\0");
2206
+ let hash = 2166136261;
2207
+ for (let i = 0; i < input.length; i++) {
2208
+ hash ^= input.charCodeAt(i);
2209
+ hash = Math.imul(hash, 16777619);
2210
+ }
2211
+ return (hash >>> 0).toString(36);
2212
+ }
2213
+ function chatTitle(messages) {
2214
+ var _a, _b;
2215
+ const first = (_b = (_a = messages.find((m) => m.role === "user")) == null ? void 0 : _a.content.trim()) != null ? _b : "";
2216
+ const line = first.replace(/\s+/g, " ");
2217
+ return line.length > TITLE_CHARS ? `${line.slice(0, TITLE_CHARS - 1)}\u2026` : line;
2218
+ }
2219
+ function storage() {
2220
+ try {
2221
+ return typeof window !== "undefined" && window.localStorage ? window.localStorage : null;
2222
+ } catch (e) {
2223
+ return null;
2224
+ }
2225
+ }
2226
+ function isSavedChat(value) {
2227
+ if (!value || typeof value !== "object") return false;
2228
+ const c = value;
2229
+ return typeof c.id === "string" && typeof c.title === "string" && typeof c.updatedAt === "number" && (c.status === "open" || c.status === "filed") && Array.isArray(c.messages);
2230
+ }
2231
+ function loadChats(key, now = Date.now()) {
2232
+ const store = storage();
2233
+ if (!store) return [];
2234
+ try {
2235
+ const raw = store.getItem(PREFIX + key);
2236
+ if (!raw) return [];
2237
+ const parsed = JSON.parse(raw);
2238
+ if (!Array.isArray(parsed)) return [];
2239
+ return parsed.filter(isSavedChat).filter((c) => now - c.updatedAt < SAVED_CHAT_MAX_AGE_MS).sort((a, b) => b.updatedAt - a.updatedAt).slice(0, MAX_SAVED_CHATS);
2240
+ } catch (e) {
2241
+ return [];
2242
+ }
2243
+ }
2244
+ function write(key, chats) {
2245
+ const store = storage();
2246
+ if (!store) return;
2247
+ try {
2248
+ if (chats.length === 0) store.removeItem(PREFIX + key);
2249
+ else store.setItem(PREFIX + key, JSON.stringify(chats));
2250
+ } catch (e) {
2251
+ }
2252
+ }
2253
+ function saveChat(key, chat, now = Date.now()) {
2254
+ const rest = loadChats(key, now).filter((c) => c.id !== chat.id);
2255
+ const next = [chat, ...rest].slice(0, MAX_SAVED_CHATS);
2256
+ write(key, next);
2257
+ return next;
2258
+ }
2259
+ function markChatFiled(key, conversationId, label, now = Date.now()) {
2260
+ const chats = loadChats(key, now);
2261
+ const hit = chats.find((c) => c.conversationId === conversationId);
2262
+ if (!hit || hit.status === "filed") return;
2263
+ write(
2264
+ key,
2265
+ chats.map((c) => c === hit ? { ...c, status: "filed", filedLabel: label, updatedAt: now } : c)
2266
+ );
2267
+ }
2268
+ function removeChat(key, id, now = Date.now()) {
2269
+ const next = loadChats(key, now).filter((c) => c.id !== id);
2270
+ write(key, next);
2271
+ return next;
2272
+ }
1895
2273
  function pickAssistImages(all) {
1896
2274
  const picked = [];
1897
2275
  let total = 0;
@@ -1918,6 +2296,18 @@ function pickAssistFiles(files) {
1918
2296
  }
1919
2297
  return picked;
1920
2298
  }
2299
+ function newChatId() {
2300
+ return `local-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
2301
+ }
2302
+ function chatAge(at2, now = Date.now()) {
2303
+ const min = Math.round((now - at2) / 6e4);
2304
+ if (min < 1) return "just now";
2305
+ if (min < 60) return `${min} min ago`;
2306
+ const hours = Math.round(min / 60);
2307
+ if (hours < 24) return `${hours} h ago`;
2308
+ if (hours < 48) return "yesterday";
2309
+ return new Date(at2).toLocaleDateString(void 0, { day: "numeric", month: "short" });
2310
+ }
1921
2311
  var STARTERS = [
1922
2312
  "Something on this page is broken",
1923
2313
  "It did the wrong thing",
@@ -2000,6 +2390,33 @@ function Avatar({
2000
2390
  }
2001
2391
  );
2002
2392
  }
2393
+ var THINKING_SLOW_SECONDS = 20;
2394
+ function ThinkingClock({ color }) {
2395
+ const [seconds, setSeconds] = useState2(0);
2396
+ useEffect2(() => {
2397
+ const started = Date.now();
2398
+ const id = setInterval(() => setSeconds(Math.floor((Date.now() - started) / 1e3)), 1e3);
2399
+ return () => clearInterval(id);
2400
+ }, []);
2401
+ return (
2402
+ // Hidden from screen readers: the bubble already says "Thinking", and a
2403
+ // number changing every second would be read out every second.
2404
+ /* @__PURE__ */ jsxs3(
2405
+ "span",
2406
+ {
2407
+ "data-gg-thinking-clock": "",
2408
+ "aria-hidden": "true",
2409
+ style: { marginLeft: "6px", fontSize: "12px", color, fontVariantNumeric: "tabular-nums" },
2410
+ children: [
2411
+ "Thinking\u2026 ",
2412
+ seconds,
2413
+ "s",
2414
+ seconds >= THINKING_SLOW_SECONDS ? " \xB7 taking longer than usual, still working" : ""
2415
+ ]
2416
+ }
2417
+ )
2418
+ );
2419
+ }
2003
2420
  function AssistSheet({
2004
2421
  assist,
2005
2422
  theme: t,
@@ -2040,14 +2457,22 @@ function AssistSheet({
2040
2457
  onDegrade,
2041
2458
  onClose,
2042
2459
  onFinish,
2043
- onReportGlitchgrabProblem
2460
+ onReportGlitchgrabProblem,
2461
+ historyKey = null
2044
2462
  }) {
2463
+ var _a;
2045
2464
  const narrow = useIsNarrow();
2046
2465
  const severityAsked = showSeverity && currentTile === "BUG";
2047
2466
  const [messages, setMessages] = useState2([]);
2048
2467
  const [input, setInput] = useState2("");
2049
2468
  const [busy, setBusy] = useState2(false);
2050
2469
  const [conversationId, setConversationId] = useState2(null);
2470
+ const chatIdRef = useRef2(newChatId());
2471
+ const [savedChats, setSavedChats] = useState2(
2472
+ () => historyKey ? loadChats(historyKey) : []
2473
+ );
2474
+ const [showChats, setShowChats] = useState2(false);
2475
+ const [viewingChat, setViewingChat] = useState2(null);
2051
2476
  const [hoveredStar, setHoveredStar] = useState2(0);
2052
2477
  const [options, setOptions] = useState2([]);
2053
2478
  const [duplicate, setDuplicate] = useState2(null);
@@ -2099,8 +2524,8 @@ function AssistSheet({
2099
2524
  if (el) el.scrollTop = el.scrollHeight;
2100
2525
  }, [messages, busy, phase]);
2101
2526
  useEffect2(() => {
2102
- var _a;
2103
- if (phase === "draft") (_a = draftRef.current) == null ? void 0 : _a.focus();
2527
+ var _a2;
2528
+ if (phase === "draft") (_a2 = draftRef.current) == null ? void 0 : _a2.focus();
2104
2529
  }, [phase]);
2105
2530
  useEffect2(() => {
2106
2531
  const el = composerRef.current;
@@ -2132,7 +2557,8 @@ function AssistSheet({
2132
2557
  onDegrade(message, retryable);
2133
2558
  }
2134
2559
  async function runTurn(history2, autoFile = false) {
2135
- var _a, _b, _c;
2560
+ var _a2, _b, _c;
2561
+ const turnChat = chatIdRef.current;
2136
2562
  setBusy(true);
2137
2563
  setAutoFileLeft(null);
2138
2564
  let result;
@@ -2169,6 +2595,7 @@ function AssistSheet({
2169
2595
  } finally {
2170
2596
  setBusy(false);
2171
2597
  }
2598
+ if (chatIdRef.current !== turnChat) return;
2172
2599
  if (result.degraded) {
2173
2600
  degradeKeepingEvents(events, result.degraded, result.retryable === true);
2174
2601
  return;
@@ -2179,7 +2606,7 @@ function AssistSheet({
2179
2606
  }
2180
2607
  const attach = result.duplicate ? { ...result.duplicate, kind: "duplicate" } : result.related ? { ...result.related, kind: "related" } : null;
2181
2608
  setDuplicate(attach);
2182
- onDuplicateChange == null ? void 0 : onDuplicateChange((_a = attach == null ? void 0 : attach.number) != null ? _a : null, attach == null ? void 0 : attach.kind);
2609
+ onDuplicateChange == null ? void 0 : onDuplicateChange((_a2 = attach == null ? void 0 : attach.number) != null ? _a2 : null, attach == null ? void 0 : attach.kind);
2183
2610
  setGlitchgrabOffer(!!result.aboutGlitchgrab && !!onReportGlitchgrabProblem && !!result.question);
2184
2611
  if (result.solved) {
2185
2612
  setSolved(result.solved);
@@ -2233,9 +2660,9 @@ function AssistSheet({
2233
2660
  }
2234
2661
  }
2235
2662
  function sendTyped(text) {
2236
- var _a;
2663
+ var _a2;
2237
2664
  const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
2238
- const checkingBack = options.includes(CONFIRM_OPTION) || (lastAssistant == null ? void 0 : lastAssistant.kind) !== "report" && isCheckBackQuestion((_a = lastAssistant == null ? void 0 : lastAssistant.content) != null ? _a : "");
2665
+ const checkingBack = options.includes(CONFIRM_OPTION) || (lastAssistant == null ? void 0 : lastAssistant.kind) !== "report" && isCheckBackQuestion((_a2 = lastAssistant == null ? void 0 : lastAssistant.content) != null ? _a2 : "");
2239
2666
  send(text, checkingBack && isAffirmativeReply(text));
2240
2667
  }
2241
2668
  function send(text, autoFile = false) {
@@ -2277,7 +2704,215 @@ function AssistSheet({
2277
2704
  flushEvents();
2278
2705
  onSend();
2279
2706
  }
2707
+ useEffect2(() => {
2708
+ if (!historyKey || !messages.some((m) => m.role === "user")) return;
2709
+ const done = submitted || phase === "solved";
2710
+ const kept = submitted || phase === "draft" ? withCurrentDraft(messages) : messages;
2711
+ setSavedChats(
2712
+ saveChat(historyKey, {
2713
+ id: chatIdRef.current,
2714
+ conversationId,
2715
+ title: chatTitle(kept),
2716
+ tile: currentTile,
2717
+ messages: kept,
2718
+ ...issueTitle ? { issueTitle } : {},
2719
+ status: done ? "filed" : "open",
2720
+ ...done ? {
2721
+ filedLabel: submitted ? submittedText != null ? submittedText : `${reportTypeLabel} sent` : "Answered \u2014 nothing was filed"
2722
+ } : {},
2723
+ updatedAt: Date.now()
2724
+ })
2725
+ );
2726
+ }, [
2727
+ historyKey,
2728
+ messages,
2729
+ conversationId,
2730
+ submitted,
2731
+ phase === "solved",
2732
+ issueTitle,
2733
+ phase === "draft" ? description : null
2734
+ ]);
2735
+ function resumeChat(chat) {
2736
+ var _a2;
2737
+ flushEvents();
2738
+ chatIdRef.current = chat.id;
2739
+ setMessages(chat.messages);
2740
+ setConversationId(chat.conversationId);
2741
+ onConversationChange == null ? void 0 : onConversationChange(chat.conversationId);
2742
+ setOptions([]);
2743
+ setDuplicate(null);
2744
+ onDuplicateChange == null ? void 0 : onDuplicateChange(null);
2745
+ setGlitchgrabOffer(false);
2746
+ setAutoFileLeft(null);
2747
+ setInput("");
2748
+ if (chat.tile !== currentTile && tiles.includes(chat.tile)) {
2749
+ onPickType(chat.tile);
2750
+ }
2751
+ const last = chat.messages[chat.messages.length - 1];
2752
+ if ((last == null ? void 0 : last.kind) === "report") {
2753
+ onDescriptionChange(last.content);
2754
+ onIssueTitleChange((_a2 = chat.issueTitle) != null ? _a2 : "");
2755
+ aiDraftRef.current = last.content;
2756
+ setPhase("draft");
2757
+ } else {
2758
+ setPhase("chat");
2759
+ }
2760
+ setShowChats(false);
2761
+ setViewingChat(null);
2762
+ }
2763
+ function startNewChat() {
2764
+ flushEvents();
2765
+ chatIdRef.current = newChatId();
2766
+ setMessages([]);
2767
+ setConversationId(null);
2768
+ onConversationChange == null ? void 0 : onConversationChange(null);
2769
+ setOptions([]);
2770
+ setDuplicate(null);
2771
+ onDuplicateChange == null ? void 0 : onDuplicateChange(null);
2772
+ setGlitchgrabOffer(false);
2773
+ setAutoFileLeft(null);
2774
+ setInput("");
2775
+ onDescriptionChange("");
2776
+ onIssueTitleChange("");
2777
+ aiDraftRef.current = null;
2778
+ setPhase("chat");
2779
+ setShowChats(false);
2780
+ setViewingChat(null);
2781
+ }
2782
+ function forgetChat(id) {
2783
+ if (!historyKey) return;
2784
+ setSavedChats(removeChat(historyKey, id));
2785
+ if (viewingChat === id) setViewingChat(null);
2786
+ }
2787
+ const otherChats = savedChats.filter((c) => c.id !== chatIdRef.current);
2788
+ const resumable = (_a = otherChats.find((c) => c.status === "open" && c.messages.length > 0)) != null ? _a : null;
2280
2789
  const empty = phase === "chat" && messages.length === 0 && !busy;
2790
+ function renderChatList() {
2791
+ return /* @__PURE__ */ jsxs3("div", { "data-gg-chat-list": "", style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
2792
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
2793
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: "13px", fontWeight: 600 }, children: "Your chats" }),
2794
+ messages.length > 0 && /* @__PURE__ */ jsx3(
2795
+ "button",
2796
+ {
2797
+ type: "button",
2798
+ "data-gg-new-chat": "",
2799
+ onClick: startNewChat,
2800
+ style: {
2801
+ border: `1px solid ${t.inputBorder}`,
2802
+ borderRadius: "999px",
2803
+ background: "transparent",
2804
+ color: t.text,
2805
+ padding: "7px 12px",
2806
+ fontSize: "12px",
2807
+ fontFamily: "inherit",
2808
+ cursor: "pointer"
2809
+ },
2810
+ children: "+ New chat"
2811
+ }
2812
+ )
2813
+ ] }),
2814
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: "11.5px", color: t.textMuted, lineHeight: 1.5 }, children: "Kept in this browser only, for 30 days." }),
2815
+ otherChats.length === 0 && /* @__PURE__ */ jsx3("span", { style: { fontSize: "12.5px", color: t.textMuted, padding: "12px 0" }, children: "No other chats yet." }),
2816
+ otherChats.map((chat) => {
2817
+ var _a2;
2818
+ const filed = chat.status === "filed";
2819
+ const open = viewingChat === chat.id;
2820
+ return /* @__PURE__ */ jsxs3(
2821
+ "div",
2822
+ {
2823
+ "data-gg-saved-chat": chat.status,
2824
+ style: {
2825
+ border: `1px solid ${t.border}`,
2826
+ borderRadius: "10px",
2827
+ padding: "10px 12px",
2828
+ display: "flex",
2829
+ flexDirection: "column",
2830
+ gap: "6px",
2831
+ minWidth: 0
2832
+ },
2833
+ children: [
2834
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px", alignItems: "flex-start", minWidth: 0 }, children: [
2835
+ /* @__PURE__ */ jsxs3(
2836
+ "button",
2837
+ {
2838
+ type: "button",
2839
+ onClick: () => filed ? setViewingChat(open ? null : chat.id) : resumeChat(chat),
2840
+ style: {
2841
+ flex: 1,
2842
+ minWidth: 0,
2843
+ textAlign: "left",
2844
+ border: "none",
2845
+ background: "transparent",
2846
+ color: t.text,
2847
+ padding: 0,
2848
+ fontFamily: "inherit",
2849
+ cursor: "pointer",
2850
+ display: "flex",
2851
+ flexDirection: "column",
2852
+ gap: "4px"
2853
+ },
2854
+ children: [
2855
+ /* @__PURE__ */ jsx3("span", { style: { fontSize: "13px", lineHeight: 1.45, overflowWrap: "anywhere" }, children: chat.title || "Untitled chat" }),
2856
+ /* @__PURE__ */ jsxs3("span", { style: { fontSize: "11.5px", color: t.textMuted }, children: [
2857
+ filed ? (_a2 = chat.filedLabel) != null ? _a2 : "Sent" : "Not sent yet \u2014 tap to continue",
2858
+ " \xB7",
2859
+ " ",
2860
+ chatAge(chat.updatedAt)
2861
+ ] })
2862
+ ]
2863
+ }
2864
+ ),
2865
+ /* @__PURE__ */ jsx3(
2866
+ "button",
2867
+ {
2868
+ type: "button",
2869
+ "aria-label": `Delete chat: ${chat.title}`,
2870
+ onClick: () => forgetChat(chat.id),
2871
+ style: {
2872
+ flexShrink: 0,
2873
+ border: "none",
2874
+ background: "transparent",
2875
+ color: t.textMuted,
2876
+ fontSize: "16px",
2877
+ lineHeight: 1,
2878
+ padding: "4px 6px",
2879
+ cursor: "pointer",
2880
+ fontFamily: "inherit"
2881
+ },
2882
+ children: "\xD7"
2883
+ }
2884
+ )
2885
+ ] }),
2886
+ open && /* @__PURE__ */ jsx3(
2887
+ "div",
2888
+ {
2889
+ "data-gg-saved-transcript": "",
2890
+ style: {
2891
+ display: "flex",
2892
+ flexDirection: "column",
2893
+ gap: "6px",
2894
+ borderTop: `1px solid ${t.border}`,
2895
+ paddingTop: "8px",
2896
+ fontSize: "12.5px",
2897
+ lineHeight: 1.5
2898
+ },
2899
+ children: chat.messages.map((m, i) => /* @__PURE__ */ jsxs3("div", { style: { minWidth: 0, overflowWrap: "anywhere" }, children: [
2900
+ /* @__PURE__ */ jsxs3("span", { style: { color: t.textMuted, fontWeight: 600 }, children: [
2901
+ m.role === "user" ? "You" : m.kind === "report" ? "Report" : "Assistant",
2902
+ ":",
2903
+ " "
2904
+ ] }),
2905
+ /* @__PURE__ */ jsx3("span", { style: { whiteSpace: "pre-wrap" }, children: m.content })
2906
+ ] }, i))
2907
+ }
2908
+ )
2909
+ ]
2910
+ },
2911
+ chat.id
2912
+ );
2913
+ })
2914
+ ] });
2915
+ }
2281
2916
  function renderKnownIssue({ title, footer }) {
2282
2917
  if (!duplicate) return null;
2283
2918
  return /* @__PURE__ */ jsxs3(
@@ -2311,8 +2946,8 @@ function AssistSheet({
2311
2946
  );
2312
2947
  }
2313
2948
  const initials2 = (reporterName != null ? reporterName : "").split(/\s+/).filter(Boolean).slice(0, 2).map((w) => {
2314
- var _a, _b;
2315
- return (_b = (_a = w[0]) == null ? void 0 : _a.toUpperCase()) != null ? _b : "";
2949
+ var _a2, _b;
2950
+ return (_b = (_a2 = w[0]) == null ? void 0 : _a2.toUpperCase()) != null ? _b : "";
2316
2951
  }).join("");
2317
2952
  const panelStyle = narrow ? {
2318
2953
  width: "100%",
@@ -2403,7 +3038,9 @@ function AssistSheet({
2403
3038
  alignItems: "center",
2404
3039
  gap: "7px",
2405
3040
  fontSize: "14px",
2406
- fontWeight: 600
3041
+ fontWeight: 600,
3042
+ // One line even with the Chats pill beside it (#567).
3043
+ whiteSpace: "nowrap"
2407
3044
  },
2408
3045
  children: [
2409
3046
  /* @__PURE__ */ jsx3(Sparkle, { color: t.accent }),
@@ -2433,34 +3070,64 @@ function AssistSheet({
2433
3070
  projectSlot ? /* @__PURE__ */ jsx3("span", { style: { marginLeft: "6px" }, children: projectSlot }) : null
2434
3071
  ] })
2435
3072
  ] }),
2436
- /* @__PURE__ */ jsx3(
2437
- "button",
2438
- {
2439
- type: "button",
2440
- "data-gg-write-myself": "",
2441
- onClick: () => leave("write_myself", `header, ${phase}`),
2442
- "aria-label": "Write it myself instead",
2443
- style: {
2444
- flexShrink: 0,
2445
- border: `1px solid ${t.inputBorder}`,
2446
- borderRadius: "999px",
2447
- background: "transparent",
2448
- color: t.textMuted,
2449
- cursor: "pointer",
2450
- fontSize: "11.5px",
2451
- lineHeight: 1,
2452
- fontFamily: "inherit",
2453
- whiteSpace: "nowrap",
2454
- // ~32px tall either way — this is the escape hatch, not a hint.
2455
- padding: "9px 11px"
2456
- },
2457
- children: "Write it myself"
2458
- }
2459
- )
3073
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "6px", flexShrink: 0 }, children: [
3074
+ historyKey && (showChats || otherChats.length > 0) && /* @__PURE__ */ jsx3(
3075
+ "button",
3076
+ {
3077
+ type: "button",
3078
+ "data-gg-chats-toggle": "",
3079
+ disabled: busy,
3080
+ "aria-pressed": showChats,
3081
+ onClick: () => {
3082
+ setShowChats((v) => !v);
3083
+ setViewingChat(null);
3084
+ },
3085
+ style: {
3086
+ flexShrink: 0,
3087
+ border: `1px solid ${showChats ? t.accent : t.inputBorder}`,
3088
+ borderRadius: "999px",
3089
+ background: "transparent",
3090
+ color: showChats ? t.accent : t.textMuted,
3091
+ cursor: busy ? "default" : "pointer",
3092
+ opacity: busy ? 0.5 : 1,
3093
+ fontSize: "11.5px",
3094
+ lineHeight: 1,
3095
+ fontFamily: "inherit",
3096
+ whiteSpace: "nowrap",
3097
+ padding: "9px 11px"
3098
+ },
3099
+ children: showChats ? "\u2190 Chat" : `Chats \xB7 ${otherChats.length}`
3100
+ }
3101
+ ),
3102
+ /* @__PURE__ */ jsx3(
3103
+ "button",
3104
+ {
3105
+ type: "button",
3106
+ "data-gg-write-myself": "",
3107
+ onClick: () => leave("write_myself", `header, ${phase}`),
3108
+ "aria-label": "Write it myself instead",
3109
+ style: {
3110
+ flexShrink: 0,
3111
+ border: `1px solid ${t.inputBorder}`,
3112
+ borderRadius: "999px",
3113
+ background: "transparent",
3114
+ color: t.textMuted,
3115
+ cursor: "pointer",
3116
+ fontSize: "11.5px",
3117
+ lineHeight: 1,
3118
+ fontFamily: "inherit",
3119
+ whiteSpace: "nowrap",
3120
+ // ~32px tall either way — this is the escape hatch, not a hint.
3121
+ padding: "9px 11px"
3122
+ },
3123
+ children: "Write it myself"
3124
+ }
3125
+ )
3126
+ ] })
2460
3127
  ]
2461
3128
  }
2462
3129
  ),
2463
- /* @__PURE__ */ jsx3(
3130
+ /* @__PURE__ */ jsxs3(
2464
3131
  "div",
2465
3132
  {
2466
3133
  ref: scrollRef,
@@ -2472,762 +3139,845 @@ function AssistSheet({
2472
3139
  display: "flex",
2473
3140
  flexDirection: "column"
2474
3141
  },
2475
- children: /* @__PURE__ */ jsxs3(
2476
- "div",
2477
- {
2478
- style: {
2479
- marginTop: "auto",
2480
- display: "flex",
2481
- flexDirection: "column",
2482
- gap: "12px",
2483
- minWidth: 0
2484
- },
2485
- children: [
2486
- phase === "type" && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
2487
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
2488
- /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
2489
- /* @__PURE__ */ jsxs3(
3142
+ children: [
3143
+ showChats && renderChatList(),
3144
+ /* @__PURE__ */ jsx3(
3145
+ "div",
3146
+ {
3147
+ style: {
3148
+ marginTop: "auto",
3149
+ display: "flex",
3150
+ flexDirection: "column",
3151
+ gap: "12px",
3152
+ minWidth: 0
3153
+ },
3154
+ children: /* @__PURE__ */ jsxs3("div", { style: { display: showChats ? "none" : "contents" }, children: [
3155
+ phase === "type" && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
3156
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
3157
+ /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
3158
+ /* @__PURE__ */ jsxs3(
3159
+ "div",
3160
+ {
3161
+ style: {
3162
+ padding: "9px 12px",
3163
+ borderRadius: "12px 12px 12px 4px",
3164
+ backgroundColor: t.bgSecondary,
3165
+ fontSize: "13px",
3166
+ lineHeight: 1.55,
3167
+ maxWidth: "88%"
3168
+ },
3169
+ children: [
3170
+ reporterName ? `Hi ${reporterName.split(" ")[0]} \u2014 w` : "W",
3171
+ "hat do you want to do? Pick one and I'll take it from there."
3172
+ ]
3173
+ }
3174
+ )
3175
+ ] }),
3176
+ /* @__PURE__ */ jsx3(
2490
3177
  "div",
2491
3178
  {
2492
3179
  style: {
2493
- padding: "9px 12px",
2494
- borderRadius: "12px 12px 12px 4px",
2495
- backgroundColor: t.bgSecondary,
2496
- fontSize: "13px",
2497
- lineHeight: 1.55,
2498
- maxWidth: "88%"
3180
+ display: "flex",
3181
+ flexWrap: "wrap",
3182
+ gap: "6px",
3183
+ paddingLeft: "32px"
2499
3184
  },
2500
- children: [
2501
- reporterName ? `Hi ${reporterName.split(" ")[0]} \u2014 w` : "W",
2502
- "hat do you want to do? Pick one and I'll take it from there."
2503
- ]
3185
+ children: tiles.map((tile) => {
3186
+ const rate = tile === "RATING";
3187
+ return /* @__PURE__ */ jsx3(
3188
+ "button",
3189
+ {
3190
+ type: "button",
3191
+ "data-gg-chip": "",
3192
+ "data-gg-tile": tile,
3193
+ onClick: () => pickType(tile),
3194
+ style: {
3195
+ display: "inline-flex",
3196
+ alignItems: "center",
3197
+ gap: "5px",
3198
+ // The rating is the only tile that isn't a report, so
3199
+ // it wears the star colour the dialog's hero row uses
3200
+ // rather than hiding among the report types.
3201
+ border: `1px solid ${rate ? "rgba(245,158,11,0.5)" : t.inputBorder}`,
3202
+ background: rate ? "rgba(245,158,11,0.08)" : "transparent",
3203
+ color: rate ? "#f59e0b" : t.textMuted,
3204
+ borderRadius: "999px",
3205
+ // 8px vertical keeps the tap target at ~32px.
3206
+ padding: "8px 12px",
3207
+ fontSize: "12px",
3208
+ fontWeight: rate ? 600 : 400,
3209
+ fontFamily: "inherit",
3210
+ cursor: "pointer"
3211
+ },
3212
+ children: rate ? "\u2605 Rate us" : getTypeLabel(tile)
3213
+ },
3214
+ tile
3215
+ );
3216
+ })
2504
3217
  }
2505
3218
  )
2506
3219
  ] }),
2507
- /* @__PURE__ */ jsx3(
2508
- "div",
2509
- {
2510
- style: {
2511
- display: "flex",
2512
- flexWrap: "wrap",
2513
- gap: "6px",
2514
- paddingLeft: "32px"
2515
- },
2516
- children: tiles.map((tile) => {
2517
- const rate = tile === "RATING";
2518
- return /* @__PURE__ */ jsx3(
3220
+ phase === "severity" && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
3221
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
3222
+ /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
3223
+ /* @__PURE__ */ jsx3(
3224
+ "div",
3225
+ {
3226
+ style: {
3227
+ padding: "9px 12px",
3228
+ borderRadius: "12px 12px 12px 4px",
3229
+ backgroundColor: t.bgSecondary,
3230
+ fontSize: "13px",
3231
+ lineHeight: 1.55,
3232
+ maxWidth: "88%"
3233
+ },
3234
+ children: "How bad is it?"
3235
+ }
3236
+ )
3237
+ ] }),
3238
+ /* @__PURE__ */ jsx3(
3239
+ "div",
3240
+ {
3241
+ role: "radiogroup",
3242
+ "aria-label": "Severity",
3243
+ style: {
3244
+ display: "flex",
3245
+ flexWrap: "wrap",
3246
+ gap: "6px",
3247
+ paddingLeft: "32px"
3248
+ },
3249
+ children: SEVERITY_LEVELS.map((s) => /* @__PURE__ */ jsx3(
2519
3250
  "button",
2520
3251
  {
2521
3252
  type: "button",
3253
+ role: "radio",
3254
+ "aria-checked": severity === s,
2522
3255
  "data-gg-chip": "",
2523
- "data-gg-tile": tile,
2524
- onClick: () => pickType(tile),
3256
+ "data-gg-severity": s,
3257
+ onClick: () => answerSeverity(s),
2525
3258
  style: {
2526
- display: "inline-flex",
2527
- alignItems: "center",
2528
- gap: "5px",
2529
- // The rating is the only tile that isn't a report, so
2530
- // it wears the star colour the dialog's hero row uses
2531
- // rather than hiding among the report types.
2532
- border: `1px solid ${rate ? "rgba(245,158,11,0.5)" : t.inputBorder}`,
2533
- background: rate ? "rgba(245,158,11,0.08)" : "transparent",
2534
- color: rate ? "#f59e0b" : t.textMuted,
3259
+ border: `1px solid ${t.inputBorder}`,
3260
+ background: "transparent",
3261
+ color: t.textMuted,
2535
3262
  borderRadius: "999px",
2536
- // 8px vertical keeps the tap target at ~32px.
3263
+ // Same as the picker and option chips — one chip size.
2537
3264
  padding: "8px 12px",
2538
3265
  fontSize: "12px",
2539
- fontWeight: rate ? 600 : 400,
2540
3266
  fontFamily: "inherit",
2541
3267
  cursor: "pointer"
2542
3268
  },
2543
- children: rate ? "\u2605 Rate us" : getTypeLabel(tile)
3269
+ children: SEVERITY_LABELS[s]
2544
3270
  },
2545
- tile
2546
- );
2547
- })
2548
- }
2549
- )
2550
- ] }),
2551
- phase === "severity" && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
2552
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
2553
- /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
2554
- /* @__PURE__ */ jsx3(
2555
- "div",
2556
- {
2557
- style: {
2558
- padding: "9px 12px",
2559
- borderRadius: "12px 12px 12px 4px",
2560
- backgroundColor: t.bgSecondary,
2561
- fontSize: "13px",
2562
- lineHeight: 1.55,
2563
- maxWidth: "88%"
2564
- },
2565
- children: "How bad is it?"
3271
+ s
3272
+ ))
2566
3273
  }
2567
3274
  )
2568
3275
  ] }),
2569
- /* @__PURE__ */ jsx3(
2570
- "div",
2571
- {
2572
- role: "radiogroup",
2573
- "aria-label": "Severity",
2574
- style: {
2575
- display: "flex",
2576
- flexWrap: "wrap",
2577
- gap: "6px",
2578
- paddingLeft: "32px"
2579
- },
2580
- children: SEVERITY_LEVELS.map((s) => /* @__PURE__ */ jsx3(
2581
- "button",
3276
+ phase === "rating" && !submitted && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
3277
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
3278
+ /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
3279
+ /* @__PURE__ */ jsx3(
3280
+ "div",
2582
3281
  {
2583
- type: "button",
2584
- role: "radio",
2585
- "aria-checked": severity === s,
2586
- "data-gg-chip": "",
2587
- "data-gg-severity": s,
2588
- onClick: () => answerSeverity(s),
2589
3282
  style: {
2590
- border: `1px solid ${t.inputBorder}`,
2591
- background: "transparent",
2592
- color: t.textMuted,
2593
- borderRadius: "999px",
2594
- // Same as the picker and option chips — one chip size.
2595
- padding: "8px 12px",
2596
- fontSize: "12px",
2597
- fontFamily: "inherit",
2598
- cursor: "pointer"
3283
+ padding: "9px 12px",
3284
+ borderRadius: "12px 12px 12px 4px",
3285
+ backgroundColor: t.bgSecondary,
3286
+ fontSize: "13px",
3287
+ lineHeight: 1.55,
3288
+ maxWidth: "88%"
2599
3289
  },
2600
- children: SEVERITY_LABELS[s]
2601
- },
2602
- s
2603
- ))
2604
- }
2605
- )
2606
- ] }),
2607
- phase === "rating" && !submitted && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
2608
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
2609
- /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
2610
- /* @__PURE__ */ jsx3(
3290
+ children: "How are we doing? Tap a star \u2014 the words are optional."
3291
+ }
3292
+ )
3293
+ ] }),
3294
+ /* @__PURE__ */ jsxs3(
2611
3295
  "div",
2612
3296
  {
2613
3297
  style: {
2614
- padding: "9px 12px",
2615
- borderRadius: "12px 12px 12px 4px",
2616
- backgroundColor: t.bgSecondary,
2617
- fontSize: "13px",
2618
- lineHeight: 1.55,
2619
- maxWidth: "88%"
3298
+ display: "flex",
3299
+ flexDirection: "column",
3300
+ gap: "10px",
3301
+ marginLeft: "32px",
3302
+ padding: "12px",
3303
+ borderRadius: "12px",
3304
+ border: "1px solid rgba(245,158,11,0.4)",
3305
+ backgroundColor: "rgba(245,158,11,0.06)"
2620
3306
  },
2621
- children: "How are we doing? Tap a star \u2014 the words are optional."
2622
- }
2623
- )
2624
- ] }),
2625
- /* @__PURE__ */ jsxs3(
2626
- "div",
2627
- {
2628
- style: {
2629
- display: "flex",
2630
- flexDirection: "column",
2631
- gap: "10px",
2632
- marginLeft: "32px",
2633
- padding: "12px",
2634
- borderRadius: "12px",
2635
- border: "1px solid rgba(245,158,11,0.4)",
2636
- backgroundColor: "rgba(245,158,11,0.06)"
2637
- },
2638
- children: [
2639
- /* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: "4px" }, role: "group", "aria-label": "Rating", children: [1, 2, 3, 4, 5].map((star) => {
2640
- const filled = star <= (hoveredStar || rating);
2641
- return /* @__PURE__ */ jsx3(
3307
+ children: [
3308
+ /* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: "4px" }, role: "group", "aria-label": "Rating", children: [1, 2, 3, 4, 5].map((star) => {
3309
+ const filled = star <= (hoveredStar || rating);
3310
+ return /* @__PURE__ */ jsx3(
3311
+ "button",
3312
+ {
3313
+ type: "button",
3314
+ "aria-label": `${star} star${star > 1 ? "s" : ""}`,
3315
+ "aria-pressed": star <= rating,
3316
+ onClick: () => onRatingChange(star),
3317
+ onMouseEnter: () => setHoveredStar(star),
3318
+ onMouseLeave: () => setHoveredStar(0),
3319
+ style: {
3320
+ border: "none",
3321
+ background: "transparent",
3322
+ padding: 0,
3323
+ cursor: "pointer",
3324
+ lineHeight: 0
3325
+ },
3326
+ children: /* @__PURE__ */ jsx3("svg", { width: "30", height: "30", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(
3327
+ "path",
3328
+ {
3329
+ d: "M12 3l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.4 6.8 19.1l1-5.8L3.5 9.2l5.9-.9L12 3z",
3330
+ fill: filled ? "#f59e0b" : "transparent",
3331
+ stroke: filled ? "#f59e0b" : t.inputBorder,
3332
+ strokeWidth: "1.5",
3333
+ strokeLinejoin: "round"
3334
+ }
3335
+ ) })
3336
+ },
3337
+ star
3338
+ );
3339
+ }) }),
3340
+ /* @__PURE__ */ jsx3(
3341
+ "textarea",
3342
+ {
3343
+ value: description,
3344
+ onChange: (e) => onDescriptionChange(e.target.value),
3345
+ rows: 3,
3346
+ placeholder: "What made it good \u2014 or what let you down? (optional)",
3347
+ style: {
3348
+ width: "100%",
3349
+ padding: "10px",
3350
+ borderRadius: "8px",
3351
+ border: `1px solid ${t.inputBorder}`,
3352
+ backgroundColor: t.inputBg,
3353
+ color: t.text,
3354
+ fontSize: "13px",
3355
+ lineHeight: 1.5,
3356
+ fontFamily: "inherit",
3357
+ resize: "vertical",
3358
+ outline: "none",
3359
+ boxSizing: "border-box"
3360
+ }
3361
+ }
3362
+ ),
3363
+ /* @__PURE__ */ jsx3(
2642
3364
  "button",
2643
3365
  {
2644
3366
  type: "button",
2645
- "aria-label": `${star} star${star > 1 ? "s" : ""}`,
2646
- "aria-pressed": star <= rating,
2647
- onClick: () => onRatingChange(star),
2648
- onMouseEnter: () => setHoveredStar(star),
2649
- onMouseLeave: () => setHoveredStar(0),
3367
+ onClick: () => setPhase("type"),
2650
3368
  style: {
3369
+ alignSelf: "flex-start",
2651
3370
  border: "none",
2652
3371
  background: "transparent",
2653
- padding: 0,
3372
+ color: t.textMuted,
3373
+ fontSize: "12px",
3374
+ fontFamily: "inherit",
3375
+ // A bare text link is ~14px tall — half a thumb.
3376
+ padding: "9px 0",
2654
3377
  cursor: "pointer",
2655
- lineHeight: 0
3378
+ textDecoration: "underline"
2656
3379
  },
2657
- children: /* @__PURE__ */ jsx3("svg", { width: "30", height: "30", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(
2658
- "path",
2659
- {
2660
- d: "M12 3l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.4 6.8 19.1l1-5.8L3.5 9.2l5.9-.9L12 3z",
2661
- fill: filled ? "#f59e0b" : "transparent",
2662
- stroke: filled ? "#f59e0b" : t.inputBorder,
2663
- strokeWidth: "1.5",
2664
- strokeLinejoin: "round"
2665
- }
2666
- ) })
2667
- },
2668
- star
2669
- );
2670
- }) }),
2671
- /* @__PURE__ */ jsx3(
2672
- "textarea",
2673
- {
2674
- value: description,
2675
- onChange: (e) => onDescriptionChange(e.target.value),
2676
- rows: 3,
2677
- placeholder: "What made it good \u2014 or what let you down? (optional)",
2678
- style: {
2679
- width: "100%",
2680
- padding: "10px",
2681
- borderRadius: "8px",
2682
- border: `1px solid ${t.inputBorder}`,
2683
- backgroundColor: t.inputBg,
2684
- color: t.text,
2685
- fontSize: "13px",
2686
- lineHeight: 1.5,
2687
- fontFamily: "inherit",
2688
- resize: "vertical",
2689
- outline: "none",
2690
- boxSizing: "border-box"
3380
+ children: "Report something instead"
2691
3381
  }
2692
- }
2693
- ),
2694
- /* @__PURE__ */ jsx3(
3382
+ )
3383
+ ]
3384
+ }
3385
+ )
3386
+ ] }),
3387
+ empty && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
3388
+ resumable && /* @__PURE__ */ jsxs3(
3389
+ "div",
3390
+ {
3391
+ "data-gg-resume-chat": "",
3392
+ style: {
3393
+ display: "flex",
3394
+ flexDirection: "column",
3395
+ gap: "8px",
3396
+ padding: "10px 12px",
3397
+ borderRadius: "10px",
3398
+ border: `1px solid ${t.accent}55`,
3399
+ backgroundColor: t.bgSecondary,
3400
+ minWidth: 0
3401
+ },
3402
+ children: [
3403
+ /* @__PURE__ */ jsxs3("span", { style: { fontSize: "11.5px", color: t.textMuted }, children: [
3404
+ "Pick up where you left off \xB7 ",
3405
+ chatAge(resumable.updatedAt)
3406
+ ] }),
3407
+ /* @__PURE__ */ jsx3(
3408
+ "span",
3409
+ {
3410
+ style: {
3411
+ fontSize: "13px",
3412
+ lineHeight: 1.45,
3413
+ overflowWrap: "anywhere"
3414
+ },
3415
+ children: resumable.title
3416
+ }
3417
+ ),
3418
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap" }, children: [
3419
+ /* @__PURE__ */ jsx3(
3420
+ "button",
3421
+ {
3422
+ type: "button",
3423
+ "data-gg-resume-continue": "",
3424
+ onClick: () => resumeChat(resumable),
3425
+ style: {
3426
+ border: "none",
3427
+ borderRadius: "999px",
3428
+ backgroundColor: t.accent,
3429
+ color: t.accentText,
3430
+ padding: "7px 13px",
3431
+ fontSize: "12px",
3432
+ fontWeight: 600,
3433
+ fontFamily: "inherit",
3434
+ cursor: "pointer"
3435
+ },
3436
+ children: "Continue this chat"
3437
+ }
3438
+ ),
3439
+ /* @__PURE__ */ jsx3(
3440
+ "button",
3441
+ {
3442
+ type: "button",
3443
+ onClick: () => setShowChats(true),
3444
+ style: {
3445
+ border: `1px solid ${t.inputBorder}`,
3446
+ borderRadius: "999px",
3447
+ background: "transparent",
3448
+ color: t.textMuted,
3449
+ padding: "7px 13px",
3450
+ fontSize: "12px",
3451
+ fontFamily: "inherit",
3452
+ cursor: "pointer"
3453
+ },
3454
+ children: "All chats"
3455
+ }
3456
+ )
3457
+ ] })
3458
+ ]
3459
+ }
3460
+ ),
3461
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
3462
+ /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
3463
+ /* @__PURE__ */ jsxs3(
3464
+ "div",
3465
+ {
3466
+ style: {
3467
+ padding: "9px 12px",
3468
+ borderRadius: "12px 12px 12px 4px",
3469
+ backgroundColor: t.bgSecondary,
3470
+ fontSize: "13px",
3471
+ lineHeight: 1.55,
3472
+ maxWidth: "88%"
3473
+ },
3474
+ children: [
3475
+ reporterName ? `Hi ${reporterName.split(" ")[0]} \u2014 t` : "T",
3476
+ "ell me what went wrong in your own words. I can already see your screenshot and what you clicked, so skip the boring parts."
3477
+ ]
3478
+ }
3479
+ )
3480
+ ] }),
3481
+ /* @__PURE__ */ jsx3(
3482
+ "div",
3483
+ {
3484
+ style: {
3485
+ display: "flex",
3486
+ flexWrap: "wrap",
3487
+ gap: "6px",
3488
+ paddingLeft: "32px"
3489
+ },
3490
+ children: STARTERS.map((s) => /* @__PURE__ */ jsx3(
2695
3491
  "button",
2696
3492
  {
2697
3493
  type: "button",
2698
- onClick: () => setPhase("type"),
3494
+ "data-gg-chip": "",
3495
+ onClick: () => {
3496
+ logEvent("starter_tap", s);
3497
+ send(s);
3498
+ },
2699
3499
  style: {
2700
- alignSelf: "flex-start",
2701
- border: "none",
3500
+ border: `1px solid ${t.inputBorder}`,
2702
3501
  background: "transparent",
2703
3502
  color: t.textMuted,
3503
+ borderRadius: "999px",
3504
+ padding: "6px 11px",
2704
3505
  fontSize: "12px",
2705
3506
  fontFamily: "inherit",
2706
- // A bare text link is ~14px tall — half a thumb.
2707
- padding: "9px 0",
2708
- cursor: "pointer",
2709
- textDecoration: "underline"
3507
+ cursor: "pointer"
2710
3508
  },
2711
- children: "Report something instead"
3509
+ children: s
3510
+ },
3511
+ s
3512
+ ))
3513
+ }
3514
+ )
3515
+ ] }),
3516
+ messages.map((m, i) => {
3517
+ const mine = m.role === "user";
3518
+ if (m.kind === "report") {
3519
+ const live = phase === "draft" && i === messages.length - 1;
3520
+ return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px", minWidth: 0 }, children: [
3521
+ /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
3522
+ /* @__PURE__ */ jsx3(
3523
+ "div",
3524
+ {
3525
+ "data-gg-draft-bubble": "",
3526
+ style: {
3527
+ maxWidth: "82%",
3528
+ minWidth: 0,
3529
+ padding: "9px 12px",
3530
+ borderRadius: "12px 12px 12px 4px",
3531
+ fontSize: "13px",
3532
+ lineHeight: 1.55,
3533
+ backgroundColor: t.bgSecondary,
3534
+ color: t.text,
3535
+ border: live ? "none" : `1px solid ${t.accent}66`
3536
+ },
3537
+ children: live ? (
3538
+ // #402: "have a read" beside a countdown reads as a
3539
+ // contradiction. Say what is about to happen instead.
3540
+ 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."
3541
+ ) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
3542
+ /* @__PURE__ */ jsx3(
3543
+ "span",
3544
+ {
3545
+ style: {
3546
+ display: "block",
3547
+ fontSize: "10px",
3548
+ fontWeight: 700,
3549
+ letterSpacing: ".06em",
3550
+ textTransform: "uppercase",
3551
+ color: t.accent,
3552
+ marginBottom: "4px"
3553
+ },
3554
+ children: "Draft so far"
3555
+ }
3556
+ ),
3557
+ /* @__PURE__ */ jsx3(
3558
+ "span",
3559
+ {
3560
+ style: {
3561
+ display: "block",
3562
+ whiteSpace: "pre-wrap",
3563
+ wordBreak: "break-word",
3564
+ maxHeight: "160px",
3565
+ overflowY: "auto",
3566
+ color: t.textMuted
3567
+ },
3568
+ children: m.content
3569
+ }
3570
+ )
3571
+ ] })
2712
3572
  }
2713
3573
  )
2714
- ]
3574
+ ] }, i);
2715
3575
  }
2716
- )
2717
- ] }),
2718
- empty && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
2719
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
2720
- /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
2721
- /* @__PURE__ */ jsxs3(
3576
+ return /* @__PURE__ */ jsxs3(
2722
3577
  "div",
2723
3578
  {
2724
3579
  style: {
2725
- padding: "9px 12px",
2726
- borderRadius: "12px 12px 12px 4px",
2727
- backgroundColor: t.bgSecondary,
2728
- fontSize: "13px",
2729
- lineHeight: 1.55,
2730
- maxWidth: "88%"
3580
+ display: "flex",
3581
+ gap: "8px",
3582
+ minWidth: 0,
3583
+ flexDirection: mine ? "row-reverse" : "row"
2731
3584
  },
2732
3585
  children: [
2733
- reporterName ? `Hi ${reporterName.split(" ")[0]} \u2014 t` : "T",
2734
- "ell me what went wrong in your own words. I can already see your screenshot and what you clicked, so skip the boring parts."
3586
+ /* @__PURE__ */ jsx3(Avatar, { who: m.role, theme: t, initials: initials2 }),
3587
+ /* @__PURE__ */ jsx3(
3588
+ "div",
3589
+ {
3590
+ style: {
3591
+ maxWidth: "82%",
3592
+ minWidth: 0,
3593
+ padding: "9px 12px",
3594
+ borderRadius: mine ? "12px 12px 4px 12px" : "12px 12px 12px 4px",
3595
+ fontSize: "13px",
3596
+ lineHeight: 1.55,
3597
+ // Markdown owns the assistant's line breaks and lists.
3598
+ whiteSpace: mine ? "pre-wrap" : "normal",
3599
+ wordBreak: "break-word",
3600
+ backgroundColor: mine ? t.accent : t.bgSecondary,
3601
+ color: mine ? t.accentText : t.text
3602
+ },
3603
+ children: mine ? m.content : /* @__PURE__ */ jsx3(ChatMarkdown, { text: m.content, theme: t })
3604
+ }
3605
+ )
2735
3606
  ]
2736
- }
2737
- )
2738
- ] }),
2739
- /* @__PURE__ */ jsx3(
3607
+ },
3608
+ i
3609
+ );
3610
+ }),
3611
+ duplicate && !busy && phase === "chat" && /* @__PURE__ */ jsx3("div", { style: { paddingLeft: "32px", minWidth: 0 }, children: renderKnownIssue({ title: false }) }),
3612
+ glitchgrabOffer && onReportGlitchgrabProblem && !busy && phase === "chat" && /* @__PURE__ */ jsx3("div", { style: { paddingLeft: "32px", minWidth: 0 }, children: /* @__PURE__ */ jsxs3(
2740
3613
  "div",
2741
3614
  {
3615
+ "data-gg-glitchgrab-offer": "",
2742
3616
  style: {
2743
3617
  display: "flex",
3618
+ flexDirection: "column",
3619
+ gap: "8px",
3620
+ padding: "10px",
3621
+ borderRadius: "8px",
3622
+ border: `1px solid ${t.accent}55`,
3623
+ backgroundColor: `${t.accent}14`,
3624
+ fontSize: "12px",
3625
+ lineHeight: 1.5
3626
+ },
3627
+ children: [
3628
+ /* @__PURE__ */ jsx3("span", { style: { color: t.text, fontWeight: 600 }, children: "This is about Glitchgrab, not this app" }),
3629
+ /* @__PURE__ */ jsx3("span", { style: { color: t.textMuted }, children: "It goes to the team that builds this reporter, with your chat attached." }),
3630
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px" }, children: [
3631
+ /* @__PURE__ */ jsx3(
3632
+ "button",
3633
+ {
3634
+ type: "button",
3635
+ "data-gg-glitchgrab-offer-send": "",
3636
+ onClick: () => {
3637
+ logEvent("glitchgrab_offer_tap");
3638
+ setGlitchgrabOffer(false);
3639
+ onReportGlitchgrabProblem(
3640
+ messages.filter((m) => m.role === "user").map((m) => m.content.trim()).filter(Boolean).join("\n\n")
3641
+ );
3642
+ },
3643
+ style: {
3644
+ padding: "8px 12px",
3645
+ borderRadius: "999px",
3646
+ border: "none",
3647
+ backgroundColor: t.accent,
3648
+ color: t.accentText,
3649
+ fontSize: "12px",
3650
+ fontWeight: 700,
3651
+ fontFamily: "inherit",
3652
+ cursor: "pointer",
3653
+ // Both stretch, so on a phone each wrapped line reads as
3654
+ // a choice rather than one button left stranded.
3655
+ flex: "1 1 auto"
3656
+ },
3657
+ children: "Send to the Glitchgrab team"
3658
+ }
3659
+ ),
3660
+ /* @__PURE__ */ jsx3(
3661
+ "button",
3662
+ {
3663
+ type: "button",
3664
+ "data-gg-glitchgrab-offer-no": "",
3665
+ onClick: () => {
3666
+ logEvent("glitchgrab_offer_declined");
3667
+ send("No \u2014 it's about this app, not the reporting tool");
3668
+ },
3669
+ style: {
3670
+ padding: "8px 12px",
3671
+ borderRadius: "999px",
3672
+ border: `1px solid ${t.inputBorder}`,
3673
+ background: "transparent",
3674
+ color: t.textMuted,
3675
+ fontSize: "12px",
3676
+ fontFamily: "inherit",
3677
+ cursor: "pointer",
3678
+ flex: "1 1 auto"
3679
+ },
3680
+ children: "No, it's about this app"
3681
+ }
3682
+ )
3683
+ ] })
3684
+ ]
3685
+ }
3686
+ ) }),
3687
+ options.length > 0 && !busy && phase === "chat" && !glitchgrabOffer && /* @__PURE__ */ jsx3(
3688
+ "div",
3689
+ {
3690
+ style: {
3691
+ display: "flex",
3692
+ // Long labels wrapped into a ragged row that always stranded
3693
+ // the last chip on a line of its own. Past ~24 characters
3694
+ // these stop being chips and become a choice list, so they
3695
+ // stack full width and read like one.
3696
+ flexDirection: options.some((o) => o.length > 24) ? "column" : "row",
3697
+ alignItems: options.some((o) => o.length > 24) ? "stretch" : "flex-start",
2744
3698
  flexWrap: "wrap",
2745
3699
  gap: "6px",
2746
3700
  paddingLeft: "32px"
2747
3701
  },
2748
- children: STARTERS.map((s) => /* @__PURE__ */ jsx3(
3702
+ children: options.map((option) => /* @__PURE__ */ jsx3(
2749
3703
  "button",
2750
3704
  {
2751
3705
  type: "button",
2752
3706
  "data-gg-chip": "",
3707
+ "data-gg-option": "",
2753
3708
  onClick: () => {
2754
- logEvent("starter_tap", s);
2755
- send(s);
3709
+ logEvent("option_tap", option);
3710
+ send(option, option === CONFIRM_OPTION);
2756
3711
  },
2757
3712
  style: {
2758
3713
  border: `1px solid ${t.inputBorder}`,
2759
3714
  background: "transparent",
2760
3715
  color: t.textMuted,
2761
3716
  borderRadius: "999px",
2762
- padding: "6px 11px",
3717
+ // Same 8px as the picker chips — one tap target size in
3718
+ // this sheet, not two.
3719
+ padding: "8px 12px",
2763
3720
  fontSize: "12px",
2764
3721
  fontFamily: "inherit",
2765
- cursor: "pointer"
3722
+ cursor: "pointer",
3723
+ textAlign: "left"
2766
3724
  },
2767
- children: s
3725
+ children: option
2768
3726
  },
2769
- s
3727
+ option
2770
3728
  ))
2771
3729
  }
2772
- )
2773
- ] }),
2774
- messages.map((m, i) => {
2775
- const mine = m.role === "user";
2776
- if (m.kind === "report") {
2777
- const live = phase === "draft" && i === messages.length - 1;
2778
- return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px", minWidth: 0 }, children: [
2779
- /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
2780
- /* @__PURE__ */ jsx3(
2781
- "div",
2782
- {
2783
- "data-gg-draft-bubble": "",
2784
- style: {
2785
- maxWidth: "82%",
2786
- minWidth: 0,
2787
- padding: "9px 12px",
2788
- borderRadius: "12px 12px 12px 4px",
2789
- fontSize: "13px",
2790
- lineHeight: 1.55,
2791
- backgroundColor: t.bgSecondary,
2792
- color: t.text,
2793
- border: live ? "none" : `1px solid ${t.accent}66`
2794
- },
2795
- children: live ? (
2796
- // #402: "have a read" beside a countdown reads as a
2797
- // contradiction. Say what is about to happen instead.
2798
- 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."
2799
- ) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
2800
- /* @__PURE__ */ jsx3(
2801
- "span",
2802
- {
2803
- style: {
2804
- display: "block",
2805
- fontSize: "10px",
2806
- fontWeight: 700,
2807
- letterSpacing: ".06em",
2808
- textTransform: "uppercase",
2809
- color: t.accent,
2810
- marginBottom: "4px"
2811
- },
2812
- children: "Draft so far"
2813
- }
2814
- ),
2815
- /* @__PURE__ */ jsx3(
2816
- "span",
2817
- {
2818
- style: {
2819
- display: "block",
2820
- whiteSpace: "pre-wrap",
2821
- wordBreak: "break-word",
2822
- maxHeight: "160px",
2823
- overflowY: "auto",
2824
- color: t.textMuted
2825
- },
2826
- children: m.content
2827
- }
2828
- )
2829
- ] })
2830
- }
2831
- )
2832
- ] }, i);
2833
- }
2834
- return /* @__PURE__ */ jsxs3(
3730
+ ),
3731
+ busy && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px", minWidth: 0 }, role: "status", "aria-label": "Thinking", children: [
3732
+ /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
3733
+ /* @__PURE__ */ jsxs3(
3734
+ "span",
3735
+ {
3736
+ style: {
3737
+ display: "inline-flex",
3738
+ alignItems: "center",
3739
+ // The "still working" line must wrap on a phone, not push
3740
+ // the bubble past the sheet's edge.
3741
+ flexWrap: "wrap",
3742
+ minWidth: 0,
3743
+ gap: "4px",
3744
+ padding: "12px",
3745
+ borderRadius: "12px 12px 12px 4px",
3746
+ backgroundColor: t.bgSecondary
3747
+ },
3748
+ children: [
3749
+ [0, 1, 2].map((i) => /* @__PURE__ */ jsx3(
3750
+ "span",
3751
+ {
3752
+ style: {
3753
+ width: "5px",
3754
+ height: "5px",
3755
+ borderRadius: "50%",
3756
+ backgroundColor: t.textMuted,
3757
+ animation: `gg-dot 1.1s ease-in-out ${i * 0.15}s infinite`
3758
+ }
3759
+ },
3760
+ i
3761
+ )),
3762
+ /* @__PURE__ */ jsx3(ThinkingClock, { color: t.textMuted })
3763
+ ]
3764
+ }
3765
+ )
3766
+ ] }),
3767
+ phase === "draft" && !submitted && /* @__PURE__ */ jsxs3(
2835
3768
  "div",
2836
3769
  {
2837
3770
  style: {
3771
+ border: `1px solid ${t.accent}`,
3772
+ borderRadius: "12px",
3773
+ backgroundColor: t.bgSecondary,
3774
+ padding: "12px",
2838
3775
  display: "flex",
2839
- gap: "8px",
3776
+ flexDirection: "column",
3777
+ gap: "10px",
2840
3778
  minWidth: 0,
2841
- flexDirection: mine ? "row-reverse" : "row"
3779
+ boxSizing: "border-box"
2842
3780
  },
2843
3781
  children: [
2844
- /* @__PURE__ */ jsx3(Avatar, { who: m.role, theme: t, initials: initials2 }),
2845
3782
  /* @__PURE__ */ jsx3(
2846
- "div",
3783
+ "span",
2847
3784
  {
2848
3785
  style: {
2849
- maxWidth: "82%",
2850
- minWidth: 0,
2851
- padding: "9px 12px",
2852
- borderRadius: mine ? "12px 12px 4px 12px" : "12px 12px 12px 4px",
2853
- fontSize: "13px",
2854
- lineHeight: 1.55,
2855
- // Markdown owns the assistant's line breaks and lists.
2856
- whiteSpace: mine ? "pre-wrap" : "normal",
2857
- wordBreak: "break-word",
2858
- backgroundColor: mine ? t.accent : t.bgSecondary,
2859
- color: mine ? t.accentText : t.text
3786
+ fontSize: "10px",
3787
+ fontWeight: 700,
3788
+ letterSpacing: ".06em",
3789
+ textTransform: "uppercase",
3790
+ color: t.accent
2860
3791
  },
2861
- children: mine ? m.content : /* @__PURE__ */ jsx3(ChatMarkdown, { text: m.content, theme: t })
3792
+ children: duplicate ? `What we'll add to #${duplicate.number}${duplicate.kind === "related" ? " as a related request" : ""} \xB7 edit anything` : "Your report \xB7 edit anything"
2862
3793
  }
2863
- )
2864
- ]
2865
- },
2866
- i
2867
- );
2868
- }),
2869
- duplicate && !busy && phase === "chat" && /* @__PURE__ */ jsx3("div", { style: { paddingLeft: "32px", minWidth: 0 }, children: renderKnownIssue({ title: false }) }),
2870
- glitchgrabOffer && onReportGlitchgrabProblem && !busy && phase === "chat" && /* @__PURE__ */ jsx3("div", { style: { paddingLeft: "32px", minWidth: 0 }, children: /* @__PURE__ */ jsxs3(
2871
- "div",
2872
- {
2873
- "data-gg-glitchgrab-offer": "",
2874
- style: {
2875
- display: "flex",
2876
- flexDirection: "column",
2877
- gap: "8px",
2878
- padding: "10px",
2879
- borderRadius: "8px",
2880
- border: `1px solid ${t.accent}55`,
2881
- backgroundColor: `${t.accent}14`,
2882
- fontSize: "12px",
2883
- lineHeight: 1.5
2884
- },
2885
- children: [
2886
- /* @__PURE__ */ jsx3("span", { style: { color: t.text, fontWeight: 600 }, children: "This is about Glitchgrab, not this app" }),
2887
- /* @__PURE__ */ jsx3("span", { style: { color: t.textMuted }, children: "It goes to the team that builds this reporter, with your chat attached." }),
2888
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px" }, children: [
3794
+ ),
3795
+ renderKnownIssue({
3796
+ title: true,
3797
+ footer: "Your details will be added to it instead of opening a new issue."
3798
+ }),
3799
+ !duplicate && issueTitle && // Wraps rather than scrolls sideways: a title cut off at the
3800
+ // box edge is the very thing #543 is about. One line of text,
3801
+ // so Enter is swallowed.
2889
3802
  /* @__PURE__ */ jsx3(
2890
- "button",
3803
+ "textarea",
2891
3804
  {
2892
- type: "button",
2893
- "data-gg-glitchgrab-offer-send": "",
2894
- onClick: () => {
2895
- logEvent("glitchgrab_offer_tap");
2896
- setGlitchgrabOffer(false);
2897
- onReportGlitchgrabProblem(
2898
- messages.filter((m) => m.role === "user").map((m) => m.content.trim()).filter(Boolean).join("\n\n")
2899
- );
3805
+ "aria-label": "Title",
3806
+ value: issueTitle,
3807
+ maxLength: 120,
3808
+ rows: 2,
3809
+ onKeyDown: (e) => {
3810
+ if (e.key === "Enter") e.preventDefault();
3811
+ },
3812
+ onChange: (e) => {
3813
+ stopAutoFile("edited the title");
3814
+ onIssueTitleChange(e.target.value.replace(/[\r\n]+/g, " "));
2900
3815
  },
2901
3816
  style: {
2902
- padding: "8px 12px",
2903
- borderRadius: "999px",
2904
- border: "none",
2905
- backgroundColor: t.accent,
2906
- color: t.accentText,
2907
- fontSize: "12px",
2908
- fontWeight: 700,
3817
+ width: "100%",
3818
+ padding: "8px 10px",
3819
+ lineHeight: 1.45,
3820
+ resize: "none",
3821
+ borderRadius: "8px",
3822
+ border: `1px solid ${t.inputBorder}`,
3823
+ backgroundColor: t.inputBg,
3824
+ color: t.text,
3825
+ fontSize: "13px",
3826
+ fontWeight: 600,
2909
3827
  fontFamily: "inherit",
2910
- cursor: "pointer",
2911
- // Both stretch, so on a phone each wrapped line reads as
2912
- // a choice rather than one button left stranded.
2913
- flex: "1 1 auto"
2914
- },
2915
- children: "Send to the Glitchgrab team"
3828
+ outline: "none",
3829
+ boxSizing: "border-box"
3830
+ }
2916
3831
  }
2917
3832
  ),
2918
3833
  /* @__PURE__ */ jsx3(
2919
- "button",
3834
+ "textarea",
2920
3835
  {
2921
- type: "button",
2922
- "data-gg-glitchgrab-offer-no": "",
2923
- onClick: () => {
2924
- logEvent("glitchgrab_offer_declined");
2925
- send("No \u2014 it's about this app, not the reporting tool");
3836
+ ref: draftRef,
3837
+ value: description,
3838
+ onChange: (e) => {
3839
+ stopAutoFile("edited the draft");
3840
+ onDescriptionChange(e.target.value);
2926
3841
  },
3842
+ rows: 6,
2927
3843
  style: {
2928
- padding: "8px 12px",
2929
- borderRadius: "999px",
3844
+ width: "100%",
3845
+ minHeight: "120px",
3846
+ padding: "10px",
3847
+ borderRadius: "8px",
2930
3848
  border: `1px solid ${t.inputBorder}`,
2931
- background: "transparent",
2932
- color: t.textMuted,
2933
- fontSize: "12px",
3849
+ backgroundColor: t.inputBg,
3850
+ color: t.text,
3851
+ fontSize: "13px",
3852
+ lineHeight: 1.55,
2934
3853
  fontFamily: "inherit",
2935
- cursor: "pointer",
2936
- flex: "1 1 auto"
2937
- },
2938
- children: "No, it's about this app"
2939
- }
2940
- )
2941
- ] })
2942
- ]
2943
- }
2944
- ) }),
2945
- options.length > 0 && !busy && phase === "chat" && !glitchgrabOffer && /* @__PURE__ */ jsx3(
2946
- "div",
2947
- {
2948
- style: {
2949
- display: "flex",
2950
- // Long labels wrapped into a ragged row that always stranded
2951
- // the last chip on a line of its own. Past ~24 characters
2952
- // these stop being chips and become a choice list, so they
2953
- // stack full width and read like one.
2954
- flexDirection: options.some((o) => o.length > 24) ? "column" : "row",
2955
- alignItems: options.some((o) => o.length > 24) ? "stretch" : "flex-start",
2956
- flexWrap: "wrap",
2957
- gap: "6px",
2958
- paddingLeft: "32px"
2959
- },
2960
- children: options.map((option) => /* @__PURE__ */ jsx3(
2961
- "button",
2962
- {
2963
- type: "button",
2964
- "data-gg-chip": "",
2965
- "data-gg-option": "",
2966
- onClick: () => {
2967
- logEvent("option_tap", option);
2968
- send(option, option === CONFIRM_OPTION);
2969
- },
2970
- style: {
2971
- border: `1px solid ${t.inputBorder}`,
2972
- background: "transparent",
2973
- color: t.textMuted,
2974
- borderRadius: "999px",
2975
- // Same 8px as the picker chips — one tap target size in
2976
- // this sheet, not two.
2977
- padding: "8px 12px",
2978
- fontSize: "12px",
2979
- fontFamily: "inherit",
2980
- cursor: "pointer",
2981
- textAlign: "left"
2982
- },
2983
- children: option
2984
- },
2985
- option
2986
- ))
2987
- }
2988
- ),
2989
- busy && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, role: "status", "aria-label": "Thinking", children: [
2990
- /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
2991
- /* @__PURE__ */ jsx3(
2992
- "span",
2993
- {
2994
- style: {
2995
- display: "inline-flex",
2996
- alignItems: "center",
2997
- gap: "4px",
2998
- padding: "12px",
2999
- borderRadius: "12px 12px 12px 4px",
3000
- backgroundColor: t.bgSecondary
3001
- },
3002
- children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx3(
3003
- "span",
3004
- {
3005
- style: {
3006
- width: "5px",
3007
- height: "5px",
3008
- borderRadius: "50%",
3009
- backgroundColor: t.textMuted,
3010
- animation: `gg-dot 1.1s ease-in-out ${i * 0.15}s infinite`
3854
+ resize: "vertical",
3855
+ outline: "none",
3856
+ boxSizing: "border-box"
3857
+ }
3011
3858
  }
3012
- },
3013
- i
3014
- ))
3859
+ ),
3860
+ severityAsked && autoFileLeft === null && /* @__PURE__ */ jsxs3("div", { children: [
3861
+ /* @__PURE__ */ jsxs3(
3862
+ "span",
3863
+ {
3864
+ style: {
3865
+ fontSize: "11.5px",
3866
+ color: t.textMuted,
3867
+ display: "block",
3868
+ marginBottom: "6px"
3869
+ },
3870
+ children: [
3871
+ "How bad is it?",
3872
+ !severity && /* @__PURE__ */ jsx3(
3873
+ "span",
3874
+ {
3875
+ style: {
3876
+ marginLeft: "6px",
3877
+ fontSize: "11px",
3878
+ color: severityRefused ? "#ef4444" : t.textMuted
3879
+ },
3880
+ children: "\xB7 required"
3881
+ }
3882
+ )
3883
+ ]
3884
+ }
3885
+ ),
3886
+ /* @__PURE__ */ jsx3(
3887
+ "div",
3888
+ {
3889
+ role: "radiogroup",
3890
+ "aria-label": "Severity",
3891
+ style: { display: "flex", gap: "6px" },
3892
+ children: SEVERITY_LEVELS.map((s) => {
3893
+ const on = severity === s;
3894
+ return /* @__PURE__ */ jsx3(
3895
+ "button",
3896
+ {
3897
+ type: "button",
3898
+ onClick: () => onSeverityChange(s),
3899
+ role: "radio",
3900
+ "aria-checked": on,
3901
+ style: {
3902
+ flex: 1,
3903
+ minWidth: 0,
3904
+ padding: "8px 2px",
3905
+ borderRadius: "8px",
3906
+ border: `1px solid ${on ? t.accent : severityRefused ? "#ef4444" : t.inputBorder}`,
3907
+ backgroundColor: on ? t.accent : "transparent",
3908
+ color: on ? t.accentText : t.textMuted,
3909
+ fontSize: "12px",
3910
+ fontWeight: 600,
3911
+ fontFamily: "inherit",
3912
+ whiteSpace: "nowrap",
3913
+ overflow: "hidden",
3914
+ textOverflow: "ellipsis",
3915
+ cursor: "pointer"
3916
+ },
3917
+ children: SEVERITY_LABELS[s]
3918
+ },
3919
+ s
3920
+ );
3921
+ })
3922
+ }
3923
+ )
3924
+ ] }),
3925
+ attachmentCount > 0 && /* @__PURE__ */ jsxs3("span", { style: { fontSize: "11.5px", color: t.textMuted }, children: [
3926
+ attachmentCount,
3927
+ " ",
3928
+ attachmentCount === 1 ? "attachment" : "attachments",
3929
+ " will be sent with this."
3930
+ ] })
3931
+ ]
3015
3932
  }
3016
- )
3017
- ] }),
3018
- phase === "draft" && !submitted && /* @__PURE__ */ jsxs3(
3019
- "div",
3020
- {
3021
- style: {
3022
- border: `1px solid ${t.accent}`,
3023
- borderRadius: "12px",
3024
- backgroundColor: t.bgSecondary,
3025
- padding: "12px",
3026
- display: "flex",
3027
- flexDirection: "column",
3028
- gap: "10px",
3029
- minWidth: 0,
3030
- boxSizing: "border-box"
3031
- },
3032
- children: [
3033
- /* @__PURE__ */ jsx3(
3034
- "span",
3035
- {
3036
- style: {
3037
- fontSize: "10px",
3038
- fontWeight: 700,
3039
- letterSpacing: ".06em",
3040
- textTransform: "uppercase",
3041
- color: t.accent
3042
- },
3043
- children: duplicate ? `What we'll add to #${duplicate.number}${duplicate.kind === "related" ? " as a related request" : ""} \xB7 edit anything` : "Your report \xB7 edit anything"
3044
- }
3045
- ),
3046
- renderKnownIssue({
3047
- title: true,
3048
- footer: "Your details will be added to it instead of opening a new issue."
3049
- }),
3050
- !duplicate && issueTitle && // Wraps rather than scrolls sideways: a title cut off at the
3051
- // box edge is the very thing #543 is about. One line of text,
3052
- // so Enter is swallowed.
3053
- /* @__PURE__ */ jsx3(
3054
- "textarea",
3055
- {
3056
- "aria-label": "Title",
3057
- value: issueTitle,
3058
- maxLength: 120,
3059
- rows: 2,
3060
- onKeyDown: (e) => {
3061
- if (e.key === "Enter") e.preventDefault();
3062
- },
3063
- onChange: (e) => {
3064
- stopAutoFile("edited the title");
3065
- onIssueTitleChange(e.target.value.replace(/[\r\n]+/g, " "));
3066
- },
3067
- style: {
3068
- width: "100%",
3069
- padding: "8px 10px",
3070
- lineHeight: 1.45,
3071
- resize: "none",
3072
- borderRadius: "8px",
3073
- border: `1px solid ${t.inputBorder}`,
3074
- backgroundColor: t.inputBg,
3075
- color: t.text,
3076
- fontSize: "13px",
3077
- fontWeight: 600,
3078
- fontFamily: "inherit",
3079
- outline: "none",
3080
- boxSizing: "border-box"
3081
- }
3082
- }
3083
- ),
3933
+ ),
3934
+ phase === "solved" && solved && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
3935
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
3936
+ /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
3084
3937
  /* @__PURE__ */ jsx3(
3085
- "textarea",
3938
+ "div",
3086
3939
  {
3087
- ref: draftRef,
3088
- value: description,
3089
- onChange: (e) => {
3090
- stopAutoFile("edited the draft");
3091
- onDescriptionChange(e.target.value);
3092
- },
3093
- rows: 6,
3094
3940
  style: {
3095
- width: "100%",
3096
- minHeight: "120px",
3097
- padding: "10px",
3098
- borderRadius: "8px",
3099
- border: `1px solid ${t.inputBorder}`,
3100
- backgroundColor: t.inputBg,
3101
- color: t.text,
3941
+ padding: "9px 12px",
3942
+ borderRadius: "12px 12px 12px 4px",
3943
+ backgroundColor: t.bgSecondary,
3102
3944
  fontSize: "13px",
3103
3945
  lineHeight: 1.55,
3104
- fontFamily: "inherit",
3105
- resize: "vertical",
3106
- outline: "none",
3107
- boxSizing: "border-box"
3108
- }
3946
+ maxWidth: "88%",
3947
+ whiteSpace: "pre-wrap"
3948
+ },
3949
+ children: solved
3109
3950
  }
3110
- ),
3111
- severityAsked && autoFileLeft === null && /* @__PURE__ */ jsxs3("div", { children: [
3112
- /* @__PURE__ */ jsxs3(
3113
- "span",
3114
- {
3115
- style: {
3116
- fontSize: "11.5px",
3117
- color: t.textMuted,
3118
- display: "block",
3119
- marginBottom: "6px"
3120
- },
3121
- children: [
3122
- "How bad is it?",
3123
- !severity && /* @__PURE__ */ jsx3(
3124
- "span",
3125
- {
3126
- style: {
3127
- marginLeft: "6px",
3128
- fontSize: "11px",
3129
- color: severityRefused ? "#ef4444" : t.textMuted
3130
- },
3131
- children: "\xB7 required"
3132
- }
3133
- )
3134
- ]
3135
- }
3136
- ),
3137
- /* @__PURE__ */ jsx3(
3138
- "div",
3139
- {
3140
- role: "radiogroup",
3141
- "aria-label": "Severity",
3142
- style: { display: "flex", gap: "6px" },
3143
- children: SEVERITY_LEVELS.map((s) => {
3144
- const on = severity === s;
3145
- return /* @__PURE__ */ jsx3(
3146
- "button",
3147
- {
3148
- type: "button",
3149
- onClick: () => onSeverityChange(s),
3150
- role: "radio",
3151
- "aria-checked": on,
3152
- style: {
3153
- flex: 1,
3154
- minWidth: 0,
3155
- padding: "8px 2px",
3156
- borderRadius: "8px",
3157
- border: `1px solid ${on ? t.accent : severityRefused ? "#ef4444" : t.inputBorder}`,
3158
- backgroundColor: on ? t.accent : "transparent",
3159
- color: on ? t.accentText : t.textMuted,
3160
- fontSize: "12px",
3161
- fontWeight: 600,
3162
- fontFamily: "inherit",
3163
- whiteSpace: "nowrap",
3164
- overflow: "hidden",
3165
- textOverflow: "ellipsis",
3166
- cursor: "pointer"
3167
- },
3168
- children: SEVERITY_LABELS[s]
3169
- },
3170
- s
3171
- );
3172
- })
3173
- }
3174
- )
3175
- ] }),
3176
- attachmentCount > 0 && /* @__PURE__ */ jsxs3("span", { style: { fontSize: "11.5px", color: t.textMuted }, children: [
3177
- attachmentCount,
3178
- " ",
3179
- attachmentCount === 1 ? "attachment" : "attachments",
3180
- " will be sent with this."
3181
- ] })
3182
- ]
3183
- }
3184
- ),
3185
- phase === "solved" && solved && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [
3186
- /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
3187
- /* @__PURE__ */ jsx3(Avatar, { who: "assistant", theme: t }),
3951
+ )
3952
+ ] }),
3188
3953
  /* @__PURE__ */ jsx3(
3189
- "div",
3954
+ "span",
3190
3955
  {
3191
- style: {
3192
- padding: "9px 12px",
3193
- borderRadius: "12px 12px 12px 4px",
3194
- backgroundColor: t.bgSecondary,
3195
- fontSize: "13px",
3196
- lineHeight: 1.55,
3197
- maxWidth: "88%",
3198
- whiteSpace: "pre-wrap"
3199
- },
3200
- children: solved
3956
+ style: { fontSize: "11.5px", color: t.textMuted, paddingLeft: "32px" },
3957
+ children: "Nothing was filed \u2014 you are all set."
3201
3958
  }
3202
3959
  )
3203
3960
  ] }),
3204
- /* @__PURE__ */ jsx3(
3205
- "span",
3961
+ submitted && /* @__PURE__ */ jsx3(
3962
+ "div",
3206
3963
  {
3207
- style: { fontSize: "11.5px", color: t.textMuted, paddingLeft: "32px" },
3208
- children: "Nothing was filed \u2014 you are all set."
3964
+ style: {
3965
+ textAlign: "center",
3966
+ padding: "24px 0",
3967
+ color: t.accent,
3968
+ fontSize: "14px",
3969
+ fontWeight: 600
3970
+ },
3971
+ children: submittedText != null ? submittedText : `${reportTypeLabel} sent. Thank you!`
3209
3972
  }
3210
3973
  )
3211
- ] }),
3212
- submitted && /* @__PURE__ */ jsx3(
3213
- "div",
3214
- {
3215
- style: {
3216
- textAlign: "center",
3217
- padding: "24px 0",
3218
- color: t.accent,
3219
- fontSize: "14px",
3220
- fontWeight: 600
3221
- },
3222
- children: submittedText != null ? submittedText : `${reportTypeLabel} sent. Thank you!`
3223
- }
3224
- )
3225
- ]
3226
- }
3227
- )
3974
+ ] })
3975
+ }
3976
+ )
3977
+ ]
3228
3978
  }
3229
3979
  ),
3230
- !submitted && phase !== "type" && phase !== "severity" && /* @__PURE__ */ jsxs3(
3980
+ !submitted && !showChats && phase !== "type" && phase !== "severity" && /* @__PURE__ */ jsxs3(
3231
3981
  "div",
3232
3982
  {
3233
3983
  style: {
@@ -3544,8 +4294,8 @@ function AssistSheet({
3544
4294
  accept: canReadPdf ? ASSIST_ATTACHMENT_ACCEPT_WITH_PDF : ASSIST_ATTACHMENT_ACCEPT,
3545
4295
  multiple: true,
3546
4296
  onChange: (e) => {
3547
- var _a;
3548
- const picked = Array.from((_a = e.target.files) != null ? _a : []);
4297
+ var _a2;
4298
+ const picked = Array.from((_a2 = e.target.files) != null ? _a2 : []);
3549
4299
  if (picked.length) {
3550
4300
  stopAutoFile("changed attachments");
3551
4301
  const images = picked.filter((f) => f.type.startsWith("image/")).length;
@@ -4523,20 +5273,20 @@ function initialsOf(name) {
4523
5273
  }).join("");
4524
5274
  }
4525
5275
  function ReporterChip({
4526
- reporter,
5276
+ reporter: reporter2,
4527
5277
  t
4528
5278
  }) {
4529
5279
  var _a;
4530
5280
  const [avatarFailed, setAvatarFailed] = useState4(false);
4531
- const avatarUrl = (reporter == null ? void 0 : reporter.avatarUrl) || "";
5281
+ const avatarUrl = (reporter2 == null ? void 0 : reporter2.avatarUrl) || "";
4532
5282
  useEffect4(() => {
4533
5283
  setAvatarFailed(false);
4534
5284
  }, [avatarUrl]);
4535
- const name = ((_a = reporter == null ? void 0 : reporter.name) == null ? void 0 : _a.trim()) || "";
5285
+ const name = ((_a = reporter2 == null ? void 0 : reporter2.name) == null ? void 0 : _a.trim()) || "";
4536
5286
  const isAnonymous = !name;
4537
5287
  const initials2 = isAnonymous ? "" : initialsOf(name);
4538
5288
  const label = isAnonymous ? "Anonymous" : name;
4539
- const title = isAnonymous ? "No reporter identity was passed to Glitchgrab \u2014 this report won't be attributed to anyone" : [name, reporter == null ? void 0 : reporter.email, reporter == null ? void 0 : reporter.role].filter(Boolean).join(" \xB7 ");
5289
+ const title = isAnonymous ? "No reporter identity was passed to Glitchgrab \u2014 this report won't be attributed to anyone" : [name, reporter2 == null ? void 0 : reporter2.email, reporter2 == null ? void 0 : reporter2.role].filter(Boolean).join(" \xB7 ");
4540
5290
  const showAvatarImage = !!avatarUrl && !avatarFailed;
4541
5291
  return /* @__PURE__ */ jsxs5(
4542
5292
  "span",
@@ -4600,7 +5350,7 @@ function ReporterChip({
4600
5350
  },
4601
5351
  children: [
4602
5352
  label,
4603
- (reporter == null ? void 0 : reporter.role) ? ` \xB7 ${reporter.role}` : ""
5353
+ (reporter2 == null ? void 0 : reporter2.role) ? ` \xB7 ${reporter2.role}` : ""
4604
5354
  ]
4605
5355
  }
4606
5356
  )
@@ -4717,11 +5467,12 @@ function ReportDialog({
4717
5467
  types,
4718
5468
  showSeverity = true,
4719
5469
  captureScreenshot = captureDefaultScreenshot,
4720
- reporter,
5470
+ reporter: reporter2,
4721
5471
  onClose,
4722
5472
  headerSlot,
4723
5473
  reportGlitchgrabProblem,
4724
- layout = "modal"
5474
+ layout = "modal",
5475
+ chatHistoryKey: chatHistoryKey2 = null
4725
5476
  }) {
4726
5477
  var _a;
4727
5478
  const sheet = layout === "sheet";
@@ -4826,15 +5577,15 @@ function ReportDialog({
4826
5577
  });
4827
5578
  };
4828
5579
  applyInert();
4829
- const observer = new MutationObserver((mutations) => {
5580
+ const observer2 = new MutationObserver((mutations) => {
4830
5581
  const structural = mutations.some(
4831
5582
  (m) => m.addedNodes.length > 0 || m.removedNodes.length > 0
4832
5583
  );
4833
5584
  if (structural) applyInert();
4834
5585
  });
4835
- observer.observe(document.body, { childList: true, subtree: true });
5586
+ observer2.observe(document.body, { childList: true, subtree: true });
4836
5587
  return () => {
4837
- observer.disconnect();
5588
+ observer2.disconnect();
4838
5589
  inerted.forEach((el) => el.removeAttribute("inert"));
4839
5590
  };
4840
5591
  }, [isOpen]);
@@ -5486,6 +6237,9 @@ function ReportDialog({
5486
6237
  setAddedAsRelated(!!duplicateOf && result.issueNumber === duplicateOf && attachKind === "related");
5487
6238
  const queued = result.status === "QUEUED" && !!result.message;
5488
6239
  setQueuedMessage(queued ? (_c = result.message) != null ? _c : null : null);
6240
+ if (chatHistoryKey2 && aiConversationId) {
6241
+ markChatFiled(chatHistoryKey2, aiConversationId, `${getTypeLabel(reportType)} sent`);
6242
+ }
5489
6243
  setSubmitted(true);
5490
6244
  setDescription("");
5491
6245
  setScreenshots([]);
@@ -7305,7 +8059,7 @@ function ReportDialog({
7305
8059
  gap: "12px"
7306
8060
  },
7307
8061
  children: [
7308
- /* @__PURE__ */ jsx5(ReporterChip, { reporter, t }),
8062
+ /* @__PURE__ */ jsx5(ReporterChip, { reporter: reporter2, t }),
7309
8063
  reportGlitchgrabProblem ? /* @__PURE__ */ jsx5(
7310
8064
  "button",
7311
8065
  {
@@ -7585,6 +8339,7 @@ function ReportDialog({
7585
8339
  setDuplicateKind(issueNumber && kind === "related" ? "related" : "duplicate");
7586
8340
  },
7587
8341
  onConversationChange: setAiConversationId,
8342
+ historyKey: chatHistoryKey2,
7588
8343
  onFinish: handleClose,
7589
8344
  rating,
7590
8345
  onRatingChange: (value) => {
@@ -7592,7 +8347,7 @@ function ReportDialog({
7592
8347
  if (validationError) setValidationError(null);
7593
8348
  },
7594
8349
  projectSlot: headerSlot,
7595
- reporterName: (_a = reporter == null ? void 0 : reporter.name) != null ? _a : null,
8350
+ reporterName: (_a = reporter2 == null ? void 0 : reporter2.name) != null ? _a : null,
7596
8351
  description,
7597
8352
  onDescriptionChange: (value) => {
7598
8353
  setDescription(value);
@@ -9179,6 +9934,9 @@ function GlitchgrabProviderInner({
9179
9934
  onError,
9180
9935
  onReportSent
9181
9936
  });
9937
+ if (process.env.NODE_ENV !== "development") {
9938
+ installHydrationCapture(reportHydrationError);
9939
+ }
9182
9940
  useEffect6(() => {
9183
9941
  if (enableBreadcrumbs) {
9184
9942
  initBreadcrumbs(maxBreadcrumbs2, { responseBodyOrigins: responseBodyOrigins2 });
@@ -9234,7 +9992,7 @@ function GlitchgrabProviderInner({
9234
9992
  return matchesIgnorePatterns(message, ignoreErrors);
9235
9993
  };
9236
9994
  const handleError = (event) => {
9237
- var _a, _b, _c, _d, _e, _f, _g;
9995
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
9238
9996
  try {
9239
9997
  const isOpaqueCrossOrigin = event.message === "Script error." || event.message === "Script error" || !event.error && !event.filename;
9240
9998
  if (isOpaqueCrossOrigin) {
@@ -9249,6 +10007,7 @@ function GlitchgrabProviderInner({
9249
10007
  if (event.filename && EXTENSION_ORIGIN_RE.test(event.filename) || ((_a = event.error) == null ? void 0 : _a.stack) && EXTENSION_ORIGIN_RE.test(event.error.stack)) {
9250
10008
  return;
9251
10009
  }
10010
+ if (isHandledHydrationError(event.error)) return;
9252
10011
  incrementErrorCount();
9253
10012
  const context = captureContext(visitedPagesRef.current);
9254
10013
  const sig = computeSignature({
@@ -9274,6 +10033,9 @@ function GlitchgrabProviderInner({
9274
10033
  filename: (_e = event.filename) != null ? _e : "",
9275
10034
  lineno: String((_f = event.lineno) != null ? _f : ""),
9276
10035
  colno: String((_g = event.colno) != null ? _g : ""),
10036
+ // The root patch could not attach (React internals moved, or a
10037
+ // root it does not know): still label it and send the DOM context.
10038
+ ...isHydrationError((_h = event.error) != null ? _h : event.message) ? hydrationMetadata((_i = event.error) != null ? _i : event.message) : {},
9277
10039
  ...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
9278
10040
  ...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
9279
10041
  ...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {},
@@ -9615,7 +10377,7 @@ function GlitchgrabProviderInner({
9615
10377
  } catch (e) {
9616
10378
  }
9617
10379
  }, [openReportDialog]);
9618
- const reporter = useMemo4(() => {
10380
+ const reporter2 = useMemo4(() => {
9619
10381
  var _a;
9620
10382
  if (!session) return null;
9621
10383
  const avatar = (_a = session.avatarUrl) != null ? _a : session.image;
@@ -9686,7 +10448,8 @@ function GlitchgrabProviderInner({
9686
10448
  transcribeAudio: aiBlockedUnsigned ? void 0 : transcribe,
9687
10449
  types,
9688
10450
  showSeverity,
9689
- reporter,
10451
+ reporter: reporter2,
10452
+ chatHistoryKey: chatHistoryKey("sdk", token, session == null ? void 0 : session.userId),
9690
10453
  headerSlot: projectName ? /* @__PURE__ */ jsxs7("div", { style: { fontSize: 11, color: "#9aa0a6" }, children: [
9691
10454
  "Reporting to ",
9692
10455
  /* @__PURE__ */ jsx7("strong", { style: { color: "#e5e5e5" }, children: projectName })