glitchgrab 1.57.0 → 1.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -37,6 +37,7 @@ function toAssistTurnResult(ok, body) {
37
37
  question: (_d = d.question) != null ? _d : null,
38
38
  options: Array.isArray(d.options) ? d.options.filter((o) => typeof o === "string") : [],
39
39
  report: (_e = d.report) != null ? _e : null,
40
+ title: typeof d.title === "string" && d.title.trim() ? d.title.trim() : null,
40
41
  duplicate: (_f = d.duplicate) != null ? _f : null,
41
42
  related: (_g = d.related) != null ? _g : null,
42
43
  solved: (_h = d.solved) != null ? _h : null,
@@ -1961,6 +1962,8 @@ function AssistSheet({
1961
1962
  reporterName,
1962
1963
  description,
1963
1964
  onDescriptionChange,
1965
+ issueTitle,
1966
+ onIssueTitleChange,
1964
1967
  severity,
1965
1968
  onSeverityChange,
1966
1969
  showSeverity,
@@ -2059,7 +2062,7 @@ function AssistSheet({
2059
2062
  onDegrade(message, retryable);
2060
2063
  }
2061
2064
  async function runTurn(history2, autoFile = false) {
2062
- var _a, _b;
2065
+ var _a, _b, _c;
2063
2066
  setBusy(true);
2064
2067
  setAutoFileLeft(null);
2065
2068
  let result;
@@ -2116,6 +2119,7 @@ function AssistSheet({
2116
2119
  }
2117
2120
  if (result.report) {
2118
2121
  onDescriptionChange(result.report);
2122
+ onIssueTitleChange((_b = result.title) != null ? _b : "");
2119
2123
  aiDraftRef.current = result.report;
2120
2124
  logEvent("draft_shown");
2121
2125
  setMessages([...history2, { role: "assistant", content: result.report, kind: "report" }]);
@@ -2128,7 +2132,7 @@ function AssistSheet({
2128
2132
  }
2129
2133
  if (result.question) {
2130
2134
  setMessages([...history2, { role: "assistant", content: result.question }]);
2131
- setOptions((_b = result.options) != null ? _b : []);
2135
+ setOptions((_c = result.options) != null ? _c : []);
2132
2136
  return;
2133
2137
  }
2134
2138
  onDegrade("The assistant had nothing to add \u2014 write your report below and send it as normal.");
@@ -2972,6 +2976,40 @@ function AssistSheet({
2972
2976
  title: true,
2973
2977
  footer: "Your details will be added to it instead of opening a new issue."
2974
2978
  }),
2979
+ !duplicate && issueTitle && // Wraps rather than scrolls sideways: a title cut off at the
2980
+ // box edge is the very thing #543 is about. One line of text,
2981
+ // so Enter is swallowed.
2982
+ /* @__PURE__ */ jsx3(
2983
+ "textarea",
2984
+ {
2985
+ "aria-label": "Title",
2986
+ value: issueTitle,
2987
+ maxLength: 120,
2988
+ rows: 2,
2989
+ onKeyDown: (e) => {
2990
+ if (e.key === "Enter") e.preventDefault();
2991
+ },
2992
+ onChange: (e) => {
2993
+ stopAutoFile("edited the title");
2994
+ onIssueTitleChange(e.target.value.replace(/[\r\n]+/g, " "));
2995
+ },
2996
+ style: {
2997
+ width: "100%",
2998
+ padding: "8px 10px",
2999
+ lineHeight: 1.45,
3000
+ resize: "none",
3001
+ borderRadius: "8px",
3002
+ border: `1px solid ${t.inputBorder}`,
3003
+ backgroundColor: t.inputBg,
3004
+ color: t.text,
3005
+ fontSize: "13px",
3006
+ fontWeight: 600,
3007
+ fontFamily: "inherit",
3008
+ outline: "none",
3009
+ boxSizing: "border-box"
3010
+ }
3011
+ }
3012
+ ),
2975
3013
  /* @__PURE__ */ jsx3(
2976
3014
  "textarea",
2977
3015
  {
@@ -4613,6 +4651,7 @@ function ReportDialog({
4613
4651
  const [pickedSimilar, setPickedSimilar] = useState4(null);
4614
4652
  const [addedToIssue, setAddedToIssue] = useState4(null);
4615
4653
  const [aiConversationId, setAiConversationId] = useState4(null);
4654
+ const [issueTitle, setIssueTitle] = useState4("");
4616
4655
  const [isEnhanced, setIsEnhanced] = useState4(false);
4617
4656
  const [originalDescription, setOriginalDescription] = useState4(
4618
4657
  null
@@ -5314,6 +5353,7 @@ function ReportDialog({
5314
5353
  }
5315
5354
  if (aiConversationId) {
5316
5355
  metadata.aiConversationId = aiConversationId;
5356
+ if (sheetUp && !duplicateOf && issueTitle.trim()) metadata.issueTitle = issueTitle.trim();
5317
5357
  }
5318
5358
  const result = await report(
5319
5359
  // The RATING branch returned above, so anything reaching here is a real
@@ -5332,6 +5372,7 @@ function ReportDialog({
5332
5372
  setScreenshots([]);
5333
5373
  setAttachments([]);
5334
5374
  setAiConversationId(null);
5375
+ setIssueTitle("");
5335
5376
  setDuplicateIssueNumber(null);
5336
5377
  setDuplicateKind("duplicate");
5337
5378
  setSimilarIssues(null);
@@ -7435,6 +7476,8 @@ function ReportDialog({
7435
7476
  setDescription(value);
7436
7477
  if (validationError) setValidationError(null);
7437
7478
  },
7479
+ issueTitle,
7480
+ onIssueTitleChange: setIssueTitle,
7438
7481
  severity,
7439
7482
  onSeverityChange: (value) => {
7440
7483
  setSeverity(value);
@@ -7600,6 +7643,16 @@ function getTypeIcon(type, color, size = 24) {
7600
7643
  // src/booking-dialog.tsx
7601
7644
  import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo3, useState as useState5 } from "react";
7602
7645
  import { Fragment as Fragment5, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
7646
+ function readQuestions(value) {
7647
+ if (!Array.isArray(value)) return [];
7648
+ return value.flatMap((row) => {
7649
+ if (!row || typeof row !== "object") return [];
7650
+ const { id, label, options, required } = row;
7651
+ if (typeof id !== "string" || typeof label !== "string" || !Array.isArray(options)) return [];
7652
+ const choices = options.filter((o) => typeof o === "string" && o !== "");
7653
+ return choices.length > 0 ? [{ id, label, options: choices, required: required !== false }] : [];
7654
+ });
7655
+ }
7603
7656
  var LIGHT = {
7604
7657
  bg: "#ffffff",
7605
7658
  pane: "#ffffff",
@@ -7753,6 +7806,10 @@ function BookingDialog({
7753
7806
  const [email, setEmail] = useState5("");
7754
7807
  const [phone, setPhone] = useState5("");
7755
7808
  const [note, setNote] = useState5("");
7809
+ const [questions, setQuestions] = useState5([]);
7810
+ const [answers, setAnswers] = useState5({});
7811
+ const [pageLoaded, setPageLoaded] = useState5(false);
7812
+ const [asked, setAsked] = useState5(false);
7756
7813
  const [code, setCode] = useState5("");
7757
7814
  const [bookingId, setBookingId] = useState5(null);
7758
7815
  const [meetUrl, setMeetUrl] = useState5(null);
@@ -7781,6 +7838,7 @@ function BookingDialog({
7781
7838
  if (!json.success) {
7782
7839
  setError((_a2 = json.error) != null ? _a2 : "Couldn't load available times");
7783
7840
  setSlotsByMonth((prev) => ({ ...prev, [target]: [] }));
7841
+ setPageLoaded(true);
7784
7842
  return;
7785
7843
  }
7786
7844
  const data = (_b2 = json.data) != null ? _b2 : {};
@@ -7793,9 +7851,12 @@ function BookingDialog({
7793
7851
  slotMinutes: typeof data.slotMinutes === "number" ? data.slotMinutes : 30
7794
7852
  });
7795
7853
  setWhatsappUrl((_c2 = data.whatsappUrl) != null ? _c2 : null);
7854
+ setQuestions(readQuestions(data.questions));
7855
+ setPageLoaded(true);
7796
7856
  } catch (e) {
7797
7857
  setError("Couldn't reach the booking service");
7798
7858
  setSlotsByMonth((prev) => ({ ...prev, [target]: [] }));
7859
+ setPageLoaded(true);
7799
7860
  }
7800
7861
  },
7801
7862
  [baseUrl, headers]
@@ -7813,6 +7874,8 @@ function BookingDialog({
7813
7874
  setBookingId(null);
7814
7875
  setError(null);
7815
7876
  setAvatarFailed(false);
7877
+ setPageLoaded(false);
7878
+ setAsked(false);
7816
7879
  }, [open]);
7817
7880
  const monthLoaded = month in slotsByMonth;
7818
7881
  useEffect5(() => {
@@ -7841,6 +7904,10 @@ function BookingDialog({
7841
7904
  const daySlots = day ? (_d = byDay.get(day)) != null ? _d : [] : [];
7842
7905
  const atFirstMonth = month <= today.slice(0, 7);
7843
7906
  const pastHorizon = monthLoaded && !error && byDay.size === 0 && !atFirstMonth;
7907
+ const given = questions.flatMap((q) => q.options.includes(answers[q.id]) ? [{ ...q, answer: answers[q.id] }] : []);
7908
+ const canContinue = questions.every((q) => !q.required || q.options.includes(answers[q.id]));
7909
+ const view = step !== "slots" ? step : !pageLoaded ? "loading" : questions.length > 0 && !asked ? "questions" : "slots";
7910
+ const roomy = step === "slots";
7844
7911
  async function startBooking() {
7845
7912
  var _a2;
7846
7913
  if (!selected) return;
@@ -7855,6 +7922,7 @@ function BookingDialog({
7855
7922
  email,
7856
7923
  phone,
7857
7924
  note,
7925
+ answers: Object.fromEntries(given.map((q) => [q.id, q.answer])),
7858
7926
  startsAt: selected.startsAt,
7859
7927
  // The zone the times were shown in, which is the one the visitor chose.
7860
7928
  timezone: zone
@@ -7905,7 +7973,7 @@ function BookingDialog({
7905
7973
  "div",
7906
7974
  {
7907
7975
  style: {
7908
- marginTop: wide ? whatsappUrl && step === "slots" ? 14 : "auto" : 0,
7976
+ marginTop: wide ? whatsappUrl && roomy ? 14 : "auto" : 0,
7909
7977
  padding: wide ? 0 : "4px 20px 18px",
7910
7978
  display: "flex",
7911
7979
  justifyContent: wide ? "flex-start" : "center"
@@ -8046,9 +8114,36 @@ function BookingDialog({
8046
8114
  selectedEnd && formatTime(selectedEnd, zone, twelve)
8047
8115
  ] })
8048
8116
  ] }) }),
8117
+ asked && given.length > 0 && /* @__PURE__ */ jsx6(Row, { icon: "check", t, children: /* @__PURE__ */ jsxs6("span", { children: [
8118
+ given.map((q) => q.answer).join(" \xB7 "),
8119
+ view === "slots" && /* @__PURE__ */ jsxs6(Fragment5, { children: [
8120
+ " ",
8121
+ /* @__PURE__ */ jsx6(
8122
+ "button",
8123
+ {
8124
+ type: "button",
8125
+ onClick: () => setAsked(false),
8126
+ className: "gg-bk-link",
8127
+ style: {
8128
+ border: 0,
8129
+ // Room for a thumb without pushing the rows apart.
8130
+ padding: "8px 6px",
8131
+ margin: "-8px 0",
8132
+ background: "transparent",
8133
+ color: t.muted,
8134
+ fontFamily: "inherit",
8135
+ fontSize: 13,
8136
+ textDecoration: "underline",
8137
+ cursor: "pointer"
8138
+ },
8139
+ children: "Change"
8140
+ }
8141
+ )
8142
+ ] })
8143
+ ] }) }),
8049
8144
  /* @__PURE__ */ jsx6(Row, { icon: "clock", t, children: formatDuration(page.slotMinutes) }),
8050
8145
  /* @__PURE__ */ jsx6(Row, { icon: "video", t, children: "Google Meet" }),
8051
- /* @__PURE__ */ jsx6(Row, { icon: "globe", t, children: step === "slots" ? (
8146
+ /* @__PURE__ */ jsx6(Row, { icon: "globe", t, children: roomy ? (
8052
8147
  // The visible label is plain text; the native select sits on top of
8053
8148
  // it, invisible. A styled select sizes itself to the longest zone
8054
8149
  // name in the list, which pushed the chevron to the pane's edge.
@@ -8068,7 +8163,7 @@ function BookingDialog({
8068
8163
  ] })
8069
8164
  ) : zone })
8070
8165
  ] }),
8071
- whatsappUrl && step === "slots" && /* @__PURE__ */ jsxs6(
8166
+ whatsappUrl && roomy && /* @__PURE__ */ jsxs6(
8072
8167
  "a",
8073
8168
  {
8074
8169
  href: whatsappUrl,
@@ -8339,6 +8434,93 @@ function BookingDialog({
8339
8434
  ]
8340
8435
  }
8341
8436
  );
8437
+ const loading = /* @__PURE__ */ jsx6(
8438
+ "div",
8439
+ {
8440
+ style: {
8441
+ flex: "1 1 auto",
8442
+ minHeight: wide ? void 0 : 200,
8443
+ display: "flex",
8444
+ alignItems: "center",
8445
+ justifyContent: "center",
8446
+ fontSize: 13,
8447
+ ...muted
8448
+ },
8449
+ children: "Loading\u2026"
8450
+ }
8451
+ );
8452
+ const ask = /* @__PURE__ */ jsx6(
8453
+ "div",
8454
+ {
8455
+ style: {
8456
+ flex: "1 1 auto",
8457
+ minWidth: 0,
8458
+ padding: wide ? "28px 32px" : "20px",
8459
+ boxSizing: "border-box",
8460
+ overflowY: wide ? "auto" : void 0
8461
+ },
8462
+ children: /* @__PURE__ */ jsxs6("div", { style: { maxWidth: 520, display: "flex", flexDirection: "column", gap: 22 }, children: [
8463
+ /* @__PURE__ */ jsxs6("div", { children: [
8464
+ /* @__PURE__ */ jsx6("div", { style: { fontSize: 16, fontWeight: 600 }, children: questions.length === 1 ? "One question first" : "A few questions first" }),
8465
+ /* @__PURE__ */ jsx6("div", { style: { fontSize: 13, lineHeight: 1.5, marginTop: 4, ...muted }, children: "So the right person takes your demo." })
8466
+ ] }),
8467
+ questions.map((q) => /* @__PURE__ */ jsxs6("div", { role: "group", "aria-label": q.label, children: [
8468
+ /* @__PURE__ */ jsxs6("div", { style: { fontSize: 14, fontWeight: 500, overflowWrap: "anywhere" }, children: [
8469
+ q.label,
8470
+ !q.required && /* @__PURE__ */ jsx6("span", { style: { fontWeight: 400, ...muted }, children: " (optional)" })
8471
+ ] }),
8472
+ /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 10 }, children: q.options.map((option) => {
8473
+ const on = answers[q.id] === option;
8474
+ return /* @__PURE__ */ jsx6(
8475
+ "button",
8476
+ {
8477
+ type: "button",
8478
+ "aria-pressed": on,
8479
+ className: on ? void 0 : "gg-bk-slot",
8480
+ onClick: () => setAnswers((prev) => {
8481
+ if (on && !q.required) {
8482
+ const rest = { ...prev };
8483
+ delete rest[q.id];
8484
+ return rest;
8485
+ }
8486
+ return { ...prev, [q.id]: option };
8487
+ }),
8488
+ style: {
8489
+ padding: "9px 14px",
8490
+ minHeight: 40,
8491
+ borderRadius: 8,
8492
+ border: `1px solid ${on ? t.ink : t.border}`,
8493
+ background: on ? t.ink : t.pane,
8494
+ color: on ? t.onInk : t.text,
8495
+ fontFamily: "inherit",
8496
+ fontSize: 14,
8497
+ fontWeight: 500,
8498
+ cursor: "pointer",
8499
+ overflowWrap: "anywhere",
8500
+ textAlign: "left"
8501
+ },
8502
+ children: option
8503
+ },
8504
+ option
8505
+ );
8506
+ }) })
8507
+ ] }, q.id)),
8508
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "8px 12px", marginTop: 2 }, children: [
8509
+ /* @__PURE__ */ jsx6(
8510
+ "button",
8511
+ {
8512
+ type: "button",
8513
+ onClick: () => setAsked(true),
8514
+ disabled: !canContinue,
8515
+ style: { ...primaryStyle(t, !canContinue), whiteSpace: "nowrap" },
8516
+ children: "See available times"
8517
+ }
8518
+ ),
8519
+ !canContinue && /* @__PURE__ */ jsx6("span", { style: { fontSize: 12, ...muted }, children: "Pick one for each to continue" })
8520
+ ] })
8521
+ ] })
8522
+ }
8523
+ );
8342
8524
  const form = /* @__PURE__ */ jsx6(
8343
8525
  "div",
8344
8526
  {
@@ -8452,6 +8634,7 @@ function BookingDialog({
8452
8634
  .gg-bk-day:hover { box-shadow: inset 0 0 0 1.5px ${t.ink}; }
8453
8635
  .gg-bk-slot:hover { border-color: ${t.ink} !important; }
8454
8636
  .gg-bk-ghost:hover { background: ${t.open} !important; }
8637
+ .gg-bk-link:hover { color: ${t.text} !important; }
8455
8638
  .gg-bk-root button:focus-visible, .gg-bk-root a:focus-visible,
8456
8639
  .gg-bk-root span:has(> select:focus-visible), .gg-bk-root input:focus-visible, .gg-bk-root textarea:focus-visible {
8457
8640
  outline: 2px solid ${t.ink}; outline-offset: 2px;
@@ -8465,11 +8648,11 @@ function BookingDialog({
8465
8648
  "aria-label": page.title,
8466
8649
  className: "gg-bk-root",
8467
8650
  style: {
8468
- width: step === "slots" ? "min(1060px, 100%)" : "min(820px, 100%)",
8651
+ width: roomy ? "min(1060px, 100%)" : "min(820px, 100%)",
8469
8652
  // A month grid should not jump in height between months; the form
8470
8653
  // steps are short and would sit in a mostly empty box.
8471
- height: wide && step === "slots" ? "min(640px, calc(100vh - 48px))" : void 0,
8472
- minHeight: wide && step !== "slots" ? 420 : void 0,
8654
+ height: wide && roomy ? "min(640px, calc(100vh - 48px))" : void 0,
8655
+ minHeight: wide && !roomy ? 420 : void 0,
8473
8656
  margin: wide ? void 0 : "auto 0",
8474
8657
  display: "flex",
8475
8658
  flexDirection: wide ? "row" : "column",
@@ -8485,7 +8668,7 @@ function BookingDialog({
8485
8668
  },
8486
8669
  children: [
8487
8670
  summary,
8488
- step === "slots" ? /* @__PURE__ */ jsxs6(Fragment5, { children: [
8671
+ view === "loading" ? loading : view === "questions" ? ask : view === "slots" ? /* @__PURE__ */ jsxs6(Fragment5, { children: [
8489
8672
  calendar,
8490
8673
  times
8491
8674
  ] }) : form,
@@ -8572,6 +8755,7 @@ function Icon({ name }) {
8572
8755
  left: /* @__PURE__ */ jsx6("path", { d: "m15 18-6-6 6-6" }),
8573
8756
  right: /* @__PURE__ */ jsx6("path", { d: "m9 18 6-6-6-6" }),
8574
8757
  down: /* @__PURE__ */ jsx6("path", { d: "m6 9 6 6 6-6" }),
8758
+ check: /* @__PURE__ */ jsx6("path", { d: "M20 6 9 17l-5-5" }),
8575
8759
  clock: /* @__PURE__ */ jsxs6(Fragment5, { children: [
8576
8760
  /* @__PURE__ */ jsx6("circle", { cx: "12", cy: "12", r: "9" }),
8577
8761
  /* @__PURE__ */ jsx6("path", { d: "M12 7v5l3 2" })