qapture2 0.9.0 → 0.9.1

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.
@@ -170,6 +170,7 @@ var DEFAULTS = {
170
170
  loginField: { en: "Username", ar: "\u0627\u0633\u0645 \u0627\u0644\u0645\u0633\u062A\u062E\u062F\u0645" },
171
171
  rtl: false,
172
172
  beta: false,
173
+ collector: null,
173
174
  visible: void 0,
174
175
  alwaysVisible: false,
175
176
  hotkey: "shift+alt+q",
@@ -342,6 +343,7 @@ function validateConfig(input) {
342
343
  preamble: null,
343
344
  rtl: DEFAULTS.rtl,
344
345
  beta: DEFAULTS.beta,
346
+ collector: null,
345
347
  visible: DEFAULTS.visible,
346
348
  alwaysVisible: DEFAULTS.alwaysVisible,
347
349
  hotkey: DEFAULTS.hotkey,
@@ -363,6 +365,7 @@ function validateConfig(input) {
363
365
  preamble: null,
364
366
  rtl: DEFAULTS.rtl,
365
367
  beta: DEFAULTS.beta,
368
+ collector: null,
366
369
  visible: DEFAULTS.visible,
367
370
  alwaysVisible: DEFAULTS.alwaysVisible,
368
371
  hotkey: DEFAULTS.hotkey,
@@ -399,6 +402,14 @@ function validateConfig(input) {
399
402
  const preamble = raw["preamble"] !== void 0 ? coercePreamble(raw["preamble"]) : null;
400
403
  const rtl = typeof raw["rtl"] === "boolean" ? raw["rtl"] : DEFAULTS.rtl;
401
404
  const beta = typeof raw["beta"] === "boolean" ? raw["beta"] : DEFAULTS.beta;
405
+ const rawCol = raw["collector"];
406
+ const collector = rawCol && typeof rawCol["url"] === "string" && typeof rawCol["token"] === "string" && typeof rawCol["project"] === "string" ? {
407
+ url: rawCol["url"],
408
+ token: rawCol["token"],
409
+ project: rawCol["project"],
410
+ campaign: typeof rawCol["campaign"] === "string" ? rawCol["campaign"] : void 0,
411
+ tester: typeof rawCol["tester"] === "string" ? rawCol["tester"] : void 0
412
+ } : DEFAULTS.collector;
402
413
  const alwaysVisible = typeof raw["alwaysVisible"] === "boolean" ? raw["alwaysVisible"] : DEFAULTS.alwaysVisible;
403
414
  const hotkey = isNonEmptyString(raw["hotkey"]) ? raw["hotkey"].trim() : DEFAULTS.hotkey;
404
415
  const captureHotkey = isNonEmptyString(raw["captureHotkey"]) ? raw["captureHotkey"].trim() : DEFAULTS.captureHotkey;
@@ -422,6 +433,7 @@ function validateConfig(input) {
422
433
  preamble,
423
434
  rtl,
424
435
  beta,
436
+ collector,
425
437
  visible,
426
438
  alwaysVisible,
427
439
  hotkey,
@@ -1699,6 +1711,98 @@ function createStorage(namespace) {
1699
1711
  return { getItem, setItem, getJSON, setJSON };
1700
1712
  }
1701
1713
 
1714
+ // src/lib/faultLog.ts
1715
+ var LIMIT = 40;
1716
+ var faults = [];
1717
+ function recordFault(where, err) {
1718
+ const what = err instanceof Error ? `${err.name}: ${err.message}` : typeof err === "string" ? err : (() => {
1719
+ try {
1720
+ return JSON.stringify(err);
1721
+ } catch {
1722
+ return String(err);
1723
+ }
1724
+ })();
1725
+ faults.push({ at: Date.now(), where, what: what.slice(0, 500) });
1726
+ if (faults.length > LIMIT) faults.splice(0, faults.length - LIMIT);
1727
+ console.warn(`[QA] ${where}:`, err);
1728
+ }
1729
+ function readFaults() {
1730
+ return [...faults].reverse();
1731
+ }
1732
+ function clearFaults() {
1733
+ faults.length = 0;
1734
+ }
1735
+ function faultsAsText(version) {
1736
+ if (!faults.length) return "No faults recorded.";
1737
+ const head = [
1738
+ `qapture ${version}`,
1739
+ typeof navigator !== "undefined" ? navigator.userAgent : "",
1740
+ typeof location !== "undefined" ? location.href.split("?")[0] : "",
1741
+ ""
1742
+ ].filter(Boolean).join("\n");
1743
+ return head + readFaults().map((f) => `${new Date(f.at).toISOString()} [${f.where}] ${f.what}`).join("\n");
1744
+ }
1745
+
1746
+ // src/lib/collector.ts
1747
+ var TIMEOUT_MS = 8e3;
1748
+ var MAX_SHOT_BYTES = 6 * 1024 * 1024;
1749
+ function blobToDataUrl(blob) {
1750
+ return new Promise((resolve) => {
1751
+ try {
1752
+ const reader = new FileReader();
1753
+ reader.onload = () => resolve(typeof reader.result === "string" ? reader.result : null);
1754
+ reader.onerror = () => resolve(null);
1755
+ reader.readAsDataURL(blob);
1756
+ } catch {
1757
+ resolve(null);
1758
+ }
1759
+ });
1760
+ }
1761
+ async function sendToCollector(note, cfg) {
1762
+ if (typeof fetch === "undefined" || !cfg?.url || !cfg.token || !cfg.project) return false;
1763
+ let shot;
1764
+ if (note.screenshot && note.screenshot.size <= MAX_SHOT_BYTES) {
1765
+ shot = await blobToDataUrl(note.screenshot) ?? void 0;
1766
+ }
1767
+ const controller = new AbortController();
1768
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
1769
+ try {
1770
+ const res = await fetch(`${cfg.url.replace(/\/$/, "")}/notes`, {
1771
+ method: "POST",
1772
+ signal: controller.signal,
1773
+ headers: {
1774
+ "content-type": "application/json",
1775
+ authorization: `Bearer ${cfg.token}`
1776
+ },
1777
+ body: JSON.stringify({
1778
+ project: cfg.project,
1779
+ // A campaign per day is the shape that matches how testing actually
1780
+ // happens, and it means nobody has to name anything.
1781
+ campaign: cfg.campaign || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
1782
+ tester: cfg.tester,
1783
+ id: note.id,
1784
+ route: note.route,
1785
+ description: note.description,
1786
+ wanted: note.wanted,
1787
+ why: note.why,
1788
+ severity: note.severity,
1789
+ origin: note.origin,
1790
+ shot
1791
+ })
1792
+ });
1793
+ if (!res.ok) {
1794
+ recordFault("collector", `server answered ${res.status}`);
1795
+ return false;
1796
+ }
1797
+ return true;
1798
+ } catch (err) {
1799
+ recordFault("collector", err);
1800
+ return false;
1801
+ } finally {
1802
+ clearTimeout(timer);
1803
+ }
1804
+ }
1805
+
1702
1806
  // src/lib/strings.ts
1703
1807
  var STR = {
1704
1808
  en: {
@@ -2874,38 +2978,6 @@ function neutralizeDocumentColors(doc, aggressive = false) {
2874
2978
  return touched;
2875
2979
  }
2876
2980
 
2877
- // src/lib/faultLog.ts
2878
- var LIMIT = 40;
2879
- var faults = [];
2880
- function recordFault(where, err) {
2881
- const what = err instanceof Error ? `${err.name}: ${err.message}` : typeof err === "string" ? err : (() => {
2882
- try {
2883
- return JSON.stringify(err);
2884
- } catch {
2885
- return String(err);
2886
- }
2887
- })();
2888
- faults.push({ at: Date.now(), where, what: what.slice(0, 500) });
2889
- if (faults.length > LIMIT) faults.splice(0, faults.length - LIMIT);
2890
- console.warn(`[QA] ${where}:`, err);
2891
- }
2892
- function readFaults() {
2893
- return [...faults].reverse();
2894
- }
2895
- function clearFaults() {
2896
- faults.length = 0;
2897
- }
2898
- function faultsAsText(version) {
2899
- if (!faults.length) return "No faults recorded.";
2900
- const head = [
2901
- `qapture ${version}`,
2902
- typeof navigator !== "undefined" ? navigator.userAgent : "",
2903
- typeof location !== "undefined" ? location.href.split("?")[0] : "",
2904
- ""
2905
- ].filter(Boolean).join("\n");
2906
- return head + readFaults().map((f) => `${new Date(f.at).toISOString()} [${f.where}] ${f.what}`).join("\n");
2907
- }
2908
-
2909
2981
  // src/lib/capture.ts
2910
2982
  var HTML2CANVAS_TIMEOUT_MS = 1e4;
2911
2983
  var CHUNK_TIMEOUT_MS = 8e3;
@@ -3348,7 +3420,7 @@ function noteCheckLine(note, index) {
3348
3420
  const where = oneLine(note.route) || "/";
3349
3421
  const wanted = oneLine(note.wanted);
3350
3422
  const seen = oneLine(note.description) || "(not described)";
3351
- const claim = wanted || `${seen} \u2014 _no expectation was given; ask before assuming one_`;
3423
+ const claim = wanted || seen;
3352
3424
  const trimmed = claim.length > 180 ? `${claim.slice(0, 177)}...` : claim;
3353
3425
  return `- [ ] **check-${index}** (\`${where}\`) \u2014 ${trimmed}`;
3354
3426
  }
@@ -3398,12 +3470,12 @@ function noteToMarkdown(note, opts) {
3398
3470
  lines.push("### Observed");
3399
3471
  lines.push("");
3400
3472
  lines.push(oneLine(note.description) ? note.description.trim() : "_(not described)_");
3401
- lines.push("");
3402
- lines.push("### Expected");
3403
- lines.push("");
3404
- lines.push(
3405
- note.wanted && oneLine(note.wanted) ? note.wanted.trim() : "_(the tester did not say what they expected instead -- ask rather than assume)_"
3406
- );
3473
+ if (note.wanted && oneLine(note.wanted)) {
3474
+ lines.push("");
3475
+ lines.push("### Expected");
3476
+ lines.push("");
3477
+ lines.push(note.wanted.trim());
3478
+ }
3407
3479
  if (note.why && oneLine(note.why)) {
3408
3480
  lines.push("");
3409
3481
  lines.push("### Why it matters");
@@ -3693,7 +3765,8 @@ Hand \`verify.md\` back with the work.
3693
3765
 
3694
3766
  ### What is in this archive
3695
3767
 
3696
- - \`notes.md\` \u2014 the points themselves. Each one carries **Observed** and **Expected** under those exact headings. They are separate on purpose: where a report leaves the expectation out, agents do not stop and ask, they pick a reading and commit to it. Where you see Expected marked as not given, **ask rather than assume**.
3768
+ - \`notes.md\` \u2014 the points themselves, under an **Observed** heading. Most carry only that: the tester writes one sentence about what is wrong, which is the right amount to ask of somebody who is not an engineer. An **Expected** heading appears where somebody stated one.
3769
+ Where a point is ambiguous, **ask \u2014 do not pick a reading and commit to it.** That failure mode is specific to you: a person would stop and check, and an agent tends to fill the gap in and carry on. One question costs a message. A confident fix to the wrong problem costs the round.
3697
3770
  - \`verify.md\` \u2014 the checklist, one unticked box per point.
3698
3771
  - \`screenshots/\` \u2014 one per point. A point marked with a screenshot caveat was re-drawn rather than photographed, so canvases, charts and maps may be blank in it; trust the words over the picture there.
3699
3772
  - \`context/\` \u2014 console, network, environment and element forensics, one file per point. Deliberately **not** in \`notes.md\`: a longer report measurably lowers the chance of the right thing getting fixed, because the two sentences that matter get buried. Open these only when something is genuinely unresolved.
@@ -4518,18 +4591,6 @@ async function requestPersistentStorage() {
4518
4591
  return false;
4519
4592
  }
4520
4593
  }
4521
- function formatBytes(bytes) {
4522
- if (!Number.isFinite(bytes) || bytes <= 0) return "0 KB";
4523
- const units = ["B", "KB", "MB", "GB", "TB"];
4524
- let value = bytes;
4525
- let i = 0;
4526
- while (value >= 1024 && i < units.length - 1) {
4527
- value /= 1024;
4528
- i++;
4529
- }
4530
- const decimals = value < 10 && i > 1 ? 1 : 0;
4531
- return `${value.toFixed(decimals)} ${units[i]}`;
4532
- }
4533
4594
  function estimateOwnBytes(notes) {
4534
4595
  let total = 0;
4535
4596
  for (const n of notes) {
@@ -5031,8 +5092,11 @@ function QaProvider({
5031
5092
  });
5032
5093
  }
5033
5094
  await syncNoteThrough(note);
5095
+ if (config.collector) {
5096
+ void sendToCollector(note, config.collector);
5097
+ }
5034
5098
  },
5035
- [idb, config.journey, config.captureContext, testAlong, testAlongSteps, notify, t, syncNoteThrough, applyNotes]
5099
+ [idb, config.journey, config.captureContext, config.collector, testAlong, testAlongSteps, notify, t, syncNoteThrough, applyNotes]
5036
5100
  );
5037
5101
  const updateNote = useCallback(
5038
5102
  async (id, patch) => {
@@ -8143,7 +8207,7 @@ function upgradeHint(latest) {
8143
8207
  }
8144
8208
 
8145
8209
  // src/version.ts
8146
- var QA_VERSION = "0.9.0" ;
8210
+ var QA_VERSION = "0.9.1" ;
8147
8211
  function Section({
8148
8212
  icon,
8149
8213
  title,
@@ -8249,8 +8313,8 @@ function SettingsSheet({ onClose }) {
8249
8313
  const syncing = sync.state === "syncing";
8250
8314
  const viaZip = sync.engine === "download";
8251
8315
  const quotaKnown = storageHealth.supported && storageHealth.quotaBytes > 0;
8252
- const usedPct = quotaKnown ? Math.min(100, Math.round(storageHealth.ratio * 100)) : 0;
8253
- const meterColor = storageHealth.level === "critical" ? "var(--qa-danger)" : storageHealth.level === "warn" ? "var(--qa-warn)" : "var(--qa-accent)";
8316
+ quotaKnown ? Math.min(100, Math.round(storageHealth.ratio * 100)) : 0;
8317
+ storageHealth.level === "critical" ? "var(--qa-danger)" : storageHealth.level === "warn" ? "var(--qa-warn)" : "var(--qa-accent)";
8254
8318
  return /* @__PURE__ */ jsxs(
8255
8319
  "div",
8256
8320
  {
@@ -8379,77 +8443,6 @@ function SettingsSheet({ onClose }) {
8379
8443
  )
8380
8444
  ] }),
8381
8445
  /* @__PURE__ */ jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
8382
- /* @__PURE__ */ jsxs(Section, { icon: "HardDrive", title: t("storage_title"), children: [
8383
- /* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("storage_explain") }),
8384
- quotaKnown && /* @__PURE__ */ jsxs(Fragment, { children: [
8385
- /* @__PURE__ */ jsx(
8386
- "div",
8387
- {
8388
- className: "qa-w-full qa-rounded-full qa-overflow-hidden qa-bg-3",
8389
- role: "img",
8390
- "aria-label": t("storage_used", {
8391
- used: formatBytes(storageHealth.usageBytes),
8392
- quota: formatBytes(storageHealth.quotaBytes)
8393
- }),
8394
- style: { height: 6 },
8395
- children: /* @__PURE__ */ jsx("div", { style: { width: `${usedPct}%`, height: "100%", background: meterColor } })
8396
- }
8397
- ),
8398
- /* @__PURE__ */ jsxs("p", { className: "qa-m-0 qa-text-10 qa-text-mid", children: [
8399
- t("storage_used", {
8400
- used: formatBytes(storageHealth.usageBytes),
8401
- quota: formatBytes(storageHealth.quotaBytes)
8402
- }),
8403
- " \xB7 ",
8404
- /* @__PURE__ */ jsxs("span", { className: "qa-text-lo", children: [
8405
- "Qapture ",
8406
- formatBytes(storageHealth.ownBytes)
8407
- ] })
8408
- ] })
8409
- ] }),
8410
- /* @__PURE__ */ jsxs("label", { className: "qa-flex qa-items-start qa-gap-2 qa-text-xs qa-text-hi", style: { cursor: "pointer" }, children: [
8411
- /* @__PURE__ */ jsx(
8412
- "input",
8413
- {
8414
- type: "checkbox",
8415
- checked: autoBackup,
8416
- onChange: (e) => setAutoBackup(e.target.checked),
8417
- style: { marginTop: 2 }
8418
- }
8419
- ),
8420
- /* @__PURE__ */ jsxs("span", { children: [
8421
- t("autosave_label"),
8422
- /* @__PURE__ */ jsx("span", { className: "qa-block qa-text-10 qa-text-lo qa-leading-relaxed", children: t("autosave_hint", { n: autoBackupEvery }) })
8423
- ] })
8424
- ] }),
8425
- /* @__PURE__ */ jsxs("div", { className: "qa-flex qa-flex-wrap qa-gap-2", children: [
8426
- storageHealth.persisted ? /* @__PURE__ */ jsxs("span", { className: "qa-inline-flex qa-items-center qa-gap-1 qa-rounded-full qa-bg-success-tint qa-text-success qa-px-2 qa-py-0.5 qa-text-10", children: [
8427
- /* @__PURE__ */ jsx(Icon, { name: "CheckCircle2", size: 11 }),
8428
- t("persist_on")
8429
- ] }) : /* @__PURE__ */ jsx(
8430
- "button",
8431
- {
8432
- type: "button",
8433
- disabled: busy,
8434
- onClick: () => void run2(requestPersistentStorage2),
8435
- className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-10 qa-text-mid",
8436
- style: { background: "transparent", cursor: "pointer" },
8437
- children: t("persist_keep")
8438
- }
8439
- ),
8440
- notes.some((n) => n.screenshot) && /* @__PURE__ */ jsx(
8441
- "button",
8442
- {
8443
- type: "button",
8444
- disabled: busy,
8445
- onClick: () => void run2(dropAllScreenshots),
8446
- className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-10 qa-text-mid",
8447
- style: { background: "transparent", cursor: "pointer" },
8448
- children: t("drop_shots")
8449
- }
8450
- )
8451
- ] })
8452
- ] }),
8453
8446
  /* @__PURE__ */ jsx("div", { className: "qa-h-px qa-bg-3 qa-mt-3 qa-mb-4" }),
8454
8447
  /* @__PURE__ */ jsxs(Section, { icon: "Camera", title: t("exact_label"), children: [
8455
8448
  /* @__PURE__ */ jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("exact_hint") }),
@@ -10014,98 +10007,6 @@ function findDuplicate(text, selector, existing) {
10014
10007
  }
10015
10008
  return null;
10016
10009
  }
10017
- function recogniser() {
10018
- if (typeof window === "undefined") return null;
10019
- const w = window;
10020
- return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
10021
- }
10022
- function VoiceButton({
10023
- onText,
10024
- onInterim
10025
- }) {
10026
- const { lang, t } = useQa();
10027
- const [listening, setListening] = useState(false);
10028
- const [failed, setFailed] = useState(null);
10029
- const ref = useRef(null);
10030
- useEffect(() => () => {
10031
- ref.current?.abort();
10032
- ref.current = null;
10033
- }, []);
10034
- const Ctor = recogniser();
10035
- if (!Ctor) return null;
10036
- const stop = () => {
10037
- ref.current?.stop();
10038
- ref.current = null;
10039
- setListening(false);
10040
- onInterim?.("");
10041
- };
10042
- const start = () => {
10043
- if (listening) {
10044
- stop();
10045
- return;
10046
- }
10047
- setFailed(null);
10048
- let rec;
10049
- try {
10050
- rec = new Ctor();
10051
- } catch {
10052
- setFailed(t("voice_failed"));
10053
- return;
10054
- }
10055
- rec.lang = lang === "ar" ? "ar-IQ" : "en-US";
10056
- rec.continuous = true;
10057
- rec.interimResults = true;
10058
- rec.onresult = (e) => {
10059
- let done = "";
10060
- let partial = "";
10061
- for (let i = e.resultIndex; i < e.results.length; i++) {
10062
- const r = e.results[i];
10063
- const text = r[0]?.transcript ?? "";
10064
- if (r.isFinal) done += text;
10065
- else partial += text;
10066
- }
10067
- if (done.trim()) onText(done.trim());
10068
- onInterim?.(partial);
10069
- };
10070
- rec.onerror = (e) => {
10071
- if (e.error && e.error !== "no-speech" && e.error !== "aborted") {
10072
- setFailed(e.error === "not-allowed" ? t("voice_denied") : t("voice_failed"));
10073
- }
10074
- setListening(false);
10075
- onInterim?.("");
10076
- };
10077
- rec.onend = () => {
10078
- setListening(false);
10079
- onInterim?.("");
10080
- };
10081
- try {
10082
- rec.start();
10083
- ref.current = rec;
10084
- setListening(true);
10085
- } catch {
10086
- setFailed(t("voice_failed"));
10087
- }
10088
- };
10089
- return /* @__PURE__ */ jsxs("span", { className: "qa-inline-flex qa-items-center qa-gap-1.5", children: [
10090
- /* @__PURE__ */ jsxs(
10091
- "button",
10092
- {
10093
- type: "button",
10094
- onClick: start,
10095
- "aria-pressed": listening,
10096
- title: listening ? t("voice_stop") : t("voice_start"),
10097
- "data-qa-voice": listening ? "listening" : "idle",
10098
- className: `qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-rounded-full qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-11 qa-focus-ring ${listening ? "qa-bg-danger-tint qa-text-danger" : "qa-bg-2 qa-text-mid"}`,
10099
- style: { cursor: "pointer" },
10100
- children: [
10101
- /* @__PURE__ */ jsx(Icon, { name: listening ? "Square" : "Mic", size: 12 }),
10102
- listening ? t("voice_stop") : t("voice_start")
10103
- ]
10104
- }
10105
- ),
10106
- failed && /* @__PURE__ */ jsx("span", { className: "qa-text-10 qa-text-danger", children: failed })
10107
- ] });
10108
- }
10109
10010
  var DRAG_THRESHOLD2 = 6;
10110
10011
  var TOUCH_DRAG_THRESHOLD = 12;
10111
10012
  var MIN_REGION_SIZE = 8;
@@ -10177,11 +10078,7 @@ function CaptureMode() {
10177
10078
  const [shot, setShot] = useState(null);
10178
10079
  const stillRef = useRef(null);
10179
10080
  const [notesFromThisShot, setNotesFromThisShot] = useState(0);
10180
- const [wanted, setWanted] = useState("");
10181
- const [why, setWhy] = useState("");
10182
- const [fixHint, setFixHint] = useState("");
10183
10081
  const [origin, setOrigin] = useState(void 0);
10184
- const [spoken, setSpoken] = useState("");
10185
10082
  const [shotEngine, setShotEngine] = useState(null);
10186
10083
  const [shotUrl, setShotUrl] = useState(null);
10187
10084
  const [capturing, setCapturing] = useState(false);
@@ -10501,9 +10398,6 @@ function CaptureMode() {
10501
10398
  setShot(null);
10502
10399
  setShotEngine(null);
10503
10400
  setDescription("");
10504
- setWanted("");
10505
- setWhy("");
10506
- setFixHint("");
10507
10401
  setSeverity("bug");
10508
10402
  setTargetForensics(void 0);
10509
10403
  setOrigin(void 0);
@@ -10526,9 +10420,6 @@ function CaptureMode() {
10526
10420
  };
10527
10421
  await addNote({
10528
10422
  description,
10529
- wanted,
10530
- why,
10531
- fixHint,
10532
10423
  screenshot: shot ?? void 0,
10533
10424
  shotEngine: shotEngine ?? void 0,
10534
10425
  target,
@@ -11201,87 +11092,24 @@ function CaptureMode() {
11201
11092
  twin.description.length > 70 ? "\u2026" : "",
11202
11093
  "\u201D"
11203
11094
  ] }),
11204
- /* @__PURE__ */ jsxs("label", { className: "qa-flex qa-flex-col qa-gap-1", children: [
11205
- /* @__PURE__ */ jsxs("span", { className: "qa-flex qa-items-center qa-justify-between qa-gap-2", children: [
11206
- /* @__PURE__ */ jsx("span", { className: "qa-text-11 qa-font-semibold qa-text-hi", children: t("q_observed") }),
11207
- /* @__PURE__ */ jsx(
11208
- VoiceButton,
11209
- {
11210
- onText: (text) => setDescription((d) => d ? `${d} ${text}` : text),
11211
- onInterim: setSpoken
11095
+ /* @__PURE__ */ jsx(
11096
+ "textarea",
11097
+ {
11098
+ ref: taRef,
11099
+ value: description,
11100
+ onChange: (e) => setDescription(e.target.value),
11101
+ onKeyDown: (e) => {
11102
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
11103
+ if ((e.altKey || e.metaKey || e.ctrlKey) && SEVERITIES2[Number(e.key) - 1]) {
11104
+ e.preventDefault();
11105
+ setSeverity(SEVERITIES2[Number(e.key) - 1]);
11212
11106
  }
11213
- )
11214
- ] }),
11215
- /* @__PURE__ */ jsx(
11216
- "textarea",
11217
- {
11218
- ref: taRef,
11219
- value: description + (spoken ? ` ${spoken}` : ""),
11220
- onChange: (e) => setDescription(e.target.value),
11221
- onKeyDown: (e) => {
11222
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
11223
- if ((e.altKey || e.metaKey || e.ctrlKey) && SEVERITIES2[Number(e.key) - 1]) {
11224
- e.preventDefault();
11225
- setSeverity(SEVERITIES2[Number(e.key) - 1]);
11226
- }
11227
- },
11228
- rows: 2,
11229
- placeholder: t("q_observed_hint"),
11230
- className: "qa-w-full qa-resize-y qa-rounded-lg qa-border qa-border-subtle qa-bg-0 qa-text-hi qa-px-2 qa-py-1.5 qa-text-sm qa-focus-ring"
11231
- }
11232
- )
11233
- ] }),
11234
- /* @__PURE__ */ jsxs("label", { className: "qa-flex qa-flex-col qa-gap-1", children: [
11235
- /* @__PURE__ */ jsxs("span", { className: "qa-flex qa-items-center qa-justify-between qa-gap-2", children: [
11236
- /* @__PURE__ */ jsx("span", { className: "qa-text-11 qa-font-semibold qa-text-hi", children: t("q_wanted") }),
11237
- /* @__PURE__ */ jsx(VoiceButton, { onText: (x) => setWanted((d) => d ? `${d} ${x}` : x) })
11238
- ] }),
11239
- /* @__PURE__ */ jsx(
11240
- "textarea",
11241
- {
11242
- value: wanted,
11243
- onChange: (e) => setWanted(e.target.value),
11244
- onKeyDown: (e) => {
11245
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
11246
- },
11247
- rows: 2,
11248
- placeholder: t("q_wanted_hint"),
11249
- className: "qa-w-full qa-resize-y qa-rounded-lg qa-border qa-border-subtle qa-bg-0 qa-text-hi qa-px-2 qa-py-1.5 qa-text-sm qa-focus-ring"
11250
- }
11251
- )
11252
- ] }),
11253
- /* @__PURE__ */ jsxs("label", { className: "qa-flex qa-flex-col qa-gap-1", children: [
11254
- /* @__PURE__ */ jsxs("span", { className: "qa-flex qa-items-center qa-justify-between qa-gap-2", children: [
11255
- /* @__PURE__ */ jsx("span", { className: "qa-text-11 qa-text-mid", children: t("q_why") }),
11256
- /* @__PURE__ */ jsx(VoiceButton, { onText: (x) => setWhy((d) => d ? `${d} ${x}` : x) })
11257
- ] }),
11258
- /* @__PURE__ */ jsx(
11259
- "textarea",
11260
- {
11261
- value: why,
11262
- onChange: (e) => setWhy(e.target.value),
11263
- onKeyDown: (e) => {
11264
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) void save(e.shiftKey);
11265
- },
11266
- rows: 1,
11267
- placeholder: t("q_why_hint"),
11268
- className: "qa-w-full qa-resize-y qa-rounded-lg qa-border qa-border-subtle qa-bg-0 qa-text-hi qa-px-2 qa-py-1.5 qa-text-sm qa-focus-ring"
11269
- }
11270
- )
11271
- ] }),
11272
- developerMode && /* @__PURE__ */ jsxs("label", { className: "qa-flex qa-flex-col qa-gap-1", children: [
11273
- /* @__PURE__ */ jsx("span", { className: "qa-text-11 qa-text-mid", children: t("q_fix") }),
11274
- /* @__PURE__ */ jsx(
11275
- "textarea",
11276
- {
11277
- value: fixHint,
11278
- onChange: (e) => setFixHint(e.target.value),
11279
- rows: 1,
11280
- placeholder: t("q_fix_hint"),
11281
- className: "qa-w-full qa-resize-y qa-rounded-lg qa-border qa-border-subtle qa-bg-0 qa-text-hi qa-px-2 qa-py-1.5 qa-text-sm qa-focus-ring"
11282
- }
11283
- )
11284
- ] }),
11107
+ },
11108
+ rows: 3,
11109
+ placeholder: t("annotate_placeholder"),
11110
+ className: "qa-w-full qa-resize-y qa-rounded-lg qa-border qa-border-subtle qa-bg-0 qa-text-hi qa-px-2 qa-py-1.5 qa-text-sm qa-focus-ring"
11111
+ }
11112
+ ),
11285
11113
  developerMode && origin && (origin.component || origin.file) && /* @__PURE__ */ jsxs("p", { className: "qa-text-10 qa-text-mid", "data-qa-origin": "true", children: [
11286
11114
  origin.component ?? "\u2014",
11287
11115
  origin.file ? ` \xB7 ${origin.file}${origin.line ? `:${origin.line}` : ""}` : ""
@@ -11533,5 +11361,5 @@ function Qapture({ config }) {
11533
11361
  }
11534
11362
 
11535
11363
  export { Qapture, deleteQaDatabase, initQaStudio };
11536
- //# sourceMappingURL=chunk-B22X5Y6U.js.map
11537
- //# sourceMappingURL=chunk-B22X5Y6U.js.map
11364
+ //# sourceMappingURL=chunk-HEHVZCSV.js.map
11365
+ //# sourceMappingURL=chunk-HEHVZCSV.js.map