qapture2 0.7.7 → 0.7.8

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.
@@ -1862,6 +1862,13 @@ var STR = {
1862
1862
  retest_not_found: "Can't find that spot on this page \u2014 open the right page first",
1863
1863
  before_label: "Before",
1864
1864
  after_label: "After",
1865
+ followup_label: "What happened this time",
1866
+ followup_add: "Say what happened",
1867
+ followup_placeholder: "What did it do this time, and how is that different from what you asked for?",
1868
+ followup_hint: "The first note stays as it is. This is added under it as round two.",
1869
+ keep_only_retest: "Keep only these {n}",
1870
+ keep_only_retest_q: "Delete the other {n}?",
1871
+ keep_only_retest_hint: "Delete every note that is not waiting to be re-tested",
1865
1872
  welcome_title: "Testing {brand}",
1866
1873
  welcome_capture: 'Click "Capture from page", then click whatever looks wrong.',
1867
1874
  welcome_say: "Say what you expected in your own words. A screenshot is taken for you.",
@@ -2060,6 +2067,13 @@ var STR = {
2060
2067
  retest_not_found: "\u062A\u0639\u0630\u0651\u0631 \u0625\u064A\u062C\u0627\u062F \u0630\u0644\u0643 \u0627\u0644\u0645\u0648\u0636\u0639 \u0641\u064A \u0647\u0630\u0647 \u0627\u0644\u0635\u0641\u062D\u0629 \u2014 \u0627\u0641\u062A\u062D \u0627\u0644\u0635\u0641\u062D\u0629 \u0627\u0644\u0635\u062D\u064A\u062D\u0629 \u0623\u0648\u0644\u064B\u0627",
2061
2068
  before_label: "\u0642\u0628\u0644",
2062
2069
  after_label: "\u0628\u0639\u062F",
2070
+ followup_label: "\u0645\u0627\u0630\u0627 \u062D\u062F\u062B \u0647\u0630\u0647 \u0627\u0644\u0645\u0631\u0629",
2071
+ followup_add: "\u0627\u0643\u062A\u0628 \u0645\u0627\u0630\u0627 \u062D\u062F\u062B",
2072
+ followup_placeholder: "\u0645\u0627\u0630\u0627 \u0641\u0639\u0644 \u0647\u0630\u0647 \u0627\u0644\u0645\u0631\u0629\u060C \u0648\u0643\u064A\u0641 \u064A\u062E\u062A\u0644\u0641 \u0630\u0644\u0643 \u0639\u0645\u0651\u0627 \u0637\u0644\u0628\u062A\u0647\u061F",
2073
+ followup_hint: "\u0627\u0644\u0645\u0644\u0627\u062D\u0638\u0629 \u0627\u0644\u0623\u0648\u0644\u0649 \u062A\u0628\u0642\u0649 \u0643\u0645\u0627 \u0647\u064A. \u0647\u0630\u0647 \u062A\u064F\u0636\u0627\u0641 \u062A\u062D\u062A\u0647\u0627 \u0643\u062C\u0648\u0644\u0629 \u062B\u0627\u0646\u064A\u0629.",
2074
+ keep_only_retest: "\u0623\u0628\u0642\u0650 \u0647\u0630\u0647 \u0627\u0644\u0640 {n} \u0641\u0642\u0637",
2075
+ keep_only_retest_q: "\u062D\u0630\u0641 \u0627\u0644\u0640 {n} \u0627\u0644\u0623\u062E\u0631\u0649\u061F",
2076
+ keep_only_retest_hint: "\u062D\u0630\u0641 \u0643\u0644 \u0645\u0644\u0627\u062D\u0638\u0629 \u0644\u064A\u0633\u062A \u0628\u0627\u0646\u062A\u0638\u0627\u0631 \u0625\u0639\u0627\u062F\u0629 \u0627\u0644\u0627\u062E\u062A\u0628\u0627\u0631",
2063
2077
  welcome_title: "\u0627\u062E\u062A\u0628\u0627\u0631 {brand}",
2064
2078
  welcome_capture: "\u0627\u0636\u063A\u0637 \xAB\u0627\u0644\u062A\u0642\u0627\u0637 \u0645\u0646 \u0627\u0644\u0635\u0641\u062D\u0629\xBB \u062B\u0645 \u0627\u0636\u063A\u0637 \u0639\u0644\u0649 \u0645\u0627 \u064A\u0628\u062F\u0648 \u062E\u0627\u0637\u0626\u064B\u0627.",
2065
2079
  welcome_say: "\u0627\u0643\u062A\u0628 \u0645\u0627 \u0643\u0646\u062A \u062A\u062A\u0648\u0642\u0639\u0647 \u0628\u0643\u0644\u0645\u0627\u062A\u0643. \u062A\u064F\u0644\u062A\u0642\u0637 \u0627\u0644\u0635\u0648\u0631\u0629 \u062A\u0644\u0642\u0627\u0626\u064A\u064B\u0627.",
@@ -2104,18 +2118,6 @@ function pick(value, lang) {
2104
2118
  if (typeof value === "string") return value;
2105
2119
  return value[lang] ?? value.en ?? "";
2106
2120
  }
2107
-
2108
- // src/lib/coverage.ts
2109
- var RISK_COLORS = {
2110
- red: "#FF6B6B",
2111
- // --qa-danger
2112
- amber: "#FBBF24",
2113
- // --qa-warn
2114
- green: "#34D399",
2115
- // --qa-success
2116
- none: "#5B616B"
2117
- // --qa-neutral
2118
- };
2119
2121
  function computeCoverage(journey, guideChecked, skipped) {
2120
2122
  const red = { total: 0, covered: 0 };
2121
2123
  const amber = { total: 0, covered: 0 };
@@ -2969,16 +2971,19 @@ function clipToPaintedArea(el, rect) {
2969
2971
  let top = rect.top;
2970
2972
  let right = rect.left + rect.width;
2971
2973
  let bottom = rect.top + rect.height;
2974
+ const doc = el.ownerDocument;
2975
+ const root2 = doc ? doc.documentElement : null;
2976
+ const body = doc ? doc.body : null;
2977
+ const rootStyle = root2 ? safeComputedStyle(root2) : null;
2978
+ const bodyStyle = body ? safeComputedStyle(body) : null;
2972
2979
  let node = el.parentElement;
2973
- while (node) {
2974
- let style;
2975
- try {
2976
- style = getComputedStyle(node);
2977
- } catch {
2978
- break;
2979
- }
2980
- const clipsX = style.overflowX !== "visible";
2981
- const clipsY = style.overflowY !== "visible";
2980
+ while (node && node !== root2) {
2981
+ const style = safeComputedStyle(node);
2982
+ if (!style) break;
2983
+ const propagatedX = node === body && rootStyle?.overflowX === "visible";
2984
+ const propagatedY = node === body && rootStyle?.overflowY === "visible";
2985
+ const clipsX = !propagatedX && style.overflowX !== "visible";
2986
+ const clipsY = !propagatedY && style.overflowY !== "visible";
2982
2987
  if (clipsX || clipsY) {
2983
2988
  const b = node.getBoundingClientRect();
2984
2989
  const padLeft = b.left + node.clientLeft;
@@ -2994,9 +2999,33 @@ function clipToPaintedArea(el, rect) {
2994
2999
  }
2995
3000
  node = node.parentElement;
2996
3001
  }
3002
+ const vpX = usedViewportOverflow(rootStyle?.overflowX, bodyStyle?.overflowX);
3003
+ const vpY = usedViewportOverflow(rootStyle?.overflowY, bodyStyle?.overflowY);
3004
+ if (vpX === "hidden" || vpX === "clip" || vpY === "hidden" || vpY === "clip") {
3005
+ const { vw, vh } = viewportSize();
3006
+ if (vpX === "hidden" || vpX === "clip") {
3007
+ left = Math.max(left, 0);
3008
+ right = Math.min(right, vw);
3009
+ }
3010
+ if (vpY === "hidden" || vpY === "clip") {
3011
+ top = Math.max(top, 0);
3012
+ bottom = Math.min(bottom, vh);
3013
+ }
3014
+ }
2997
3015
  if (right - left < 2 || bottom - top < 2) return rect;
2998
3016
  return { left, top, width: right - left, height: bottom - top };
2999
3017
  }
3018
+ function safeComputedStyle(node) {
3019
+ try {
3020
+ return getComputedStyle(node);
3021
+ } catch {
3022
+ return null;
3023
+ }
3024
+ }
3025
+ function usedViewportOverflow(rootValue, bodyValue) {
3026
+ if (rootValue && rootValue !== "visible") return rootValue;
3027
+ return bodyValue || "visible";
3028
+ }
3000
3029
  function intersectViewport(rect) {
3001
3030
  const { vw, vh } = viewportSize();
3002
3031
  const left = Math.max(0, rect.left);
@@ -3191,6 +3220,16 @@ function noteToMarkdown(note, opts) {
3191
3220
  }
3192
3221
  lines.push("");
3193
3222
  lines.push(oneLine(note.description) ? note.description.trim() : "_(no description)_");
3223
+ if (note.followUp && oneLine(note.followUp)) {
3224
+ lines.push("");
3225
+ lines.push(
3226
+ "> **This one came back \u2014 round 2.** The point above was already worked on once and it is still not right. What follows is the tester re-testing it, so treat it as the current ask and the point above as the history of what was originally wanted."
3227
+ );
3228
+ lines.push("");
3229
+ lines.push(`**What happened this time**${note.followUpAt ? ` (${oneLine(note.followUpAt)})` : ""}`);
3230
+ lines.push("");
3231
+ lines.push(note.followUp.trim());
3232
+ }
3194
3233
  const recordedSteps = note.context?.steps ?? [];
3195
3234
  if (recordedSteps.length) {
3196
3235
  const t0 = Date.parse(note.timestamp) || recordedSteps[recordedSteps.length - 1].t;
@@ -3277,7 +3316,7 @@ function fmtPct(n, d) {
3277
3316
  }
3278
3317
  function summariseSession(notes, stamp) {
3279
3318
  if (!notes.length) return "No points captured.";
3280
- let bugs = 0, enhancements = 0, designs = 0, verified = 0, awaiting = 0;
3319
+ let bugs = 0, enhancements = 0, designs = 0, verified = 0, awaiting = 0, secondRound = 0;
3281
3320
  const pages = /* @__PURE__ */ new Set();
3282
3321
  let earliest = Number.POSITIVE_INFINITY;
3283
3322
  let latest = 0;
@@ -3289,6 +3328,7 @@ function summariseSession(notes, stamp) {
3289
3328
  const status = n.status ?? "open";
3290
3329
  if (status === "verified") verified++;
3291
3330
  else if (status === "fixed") awaiting++;
3331
+ if (n.followUp && n.followUp.trim()) secondRound++;
3292
3332
  pages.add((n.route || "/").split("?")[0]);
3293
3333
  const t = Date.parse(n.timestamp);
3294
3334
  if (Number.isFinite(t)) {
@@ -3304,6 +3344,7 @@ function summariseSession(notes, stamp) {
3304
3344
  if (designs) parts.push(`${designs} design`);
3305
3345
  if (verified) parts.push(`${verified} verified`);
3306
3346
  if (awaiting) parts.push(`${awaiting} awaiting re-test`);
3347
+ if (secondRound) parts.push(`${secondRound} back for a second round`);
3307
3348
  parts.push(`${pages.size} page${pages.size === 1 ? "" : "s"}`);
3308
3349
  if (Number.isFinite(earliest) && latest > earliest) {
3309
3350
  const minutes = Math.max(1, Math.round((latest - earliest) / 6e4));
@@ -3358,6 +3399,14 @@ The tester wants something added or changed: a new section, a new feature, a dif
3358
3399
 
3359
3400
  Push back only when you have a real reason: it would break something, or it conflicts with an invariant below. Then say so plainly and propose the alternative you would build instead.
3360
3401
 
3402
+ ### A point with a "round 2" on it
3403
+
3404
+ Some points carry a **round 2** block: the same finding, already worked on once, re-tested, and still not right. Two things follow from that.
3405
+
3406
+ First, **the round-2 text is the current ask** and the paragraph above it is history \u2014 read the history to understand what was wanted, act on the round 2.
3407
+
3408
+ Second, **the previous attempt is evidence.** Something about the first reading was wrong, so do not simply do it again more carefully. Work out what was misunderstood the first time and say so in one sentence, then fix that. If a "Before"/"After" screenshot pair is attached, the After is what the tester was looking at when they wrote the round 2.
3409
+
3361
3410
  ### When the tag and the words disagree
3362
3411
 
3363
3412
  The tag sets your starting posture; **the words win.** A point tagged Design that says "this crashes" is a bug \u2014 treat it as one, and mention that you changed lane. People pick the wrong chip all the time; that is not a reason to do the wrong work.
@@ -4602,6 +4651,11 @@ function QaProvider({
4602
4651
  }
4603
4652
  if (patch.severity !== void 0) updated.severity = patch.severity;
4604
4653
  if (patch.status !== void 0) updated.status = patch.status;
4654
+ if (patch.followUp !== void 0) {
4655
+ const text = patch.followUp.trim();
4656
+ updated.followUp = text || void 0;
4657
+ updated.followUpAt = text ? nowIso() : void 0;
4658
+ }
4605
4659
  applyNotes((prev) => prev.map((n) => n.id === id ? updated : n));
4606
4660
  const persisted = await idb.put(updated);
4607
4661
  if (!persisted) {
@@ -6597,6 +6651,8 @@ function NoteItem({
6597
6651
  const thumbUrl = useObjectUrl(editing ? img ?? void 0 : note.screenshot);
6598
6652
  const afterUrl = useObjectUrl(note.afterScreenshot);
6599
6653
  const [retesting, setRetesting] = useState(false);
6654
+ const [editingFollowUp, setEditingFollowUp] = useState(false);
6655
+ const [followUpDraft, setFollowUpDraft] = useState(note.followUp ?? "");
6600
6656
  const [open, setOpen] = useState(false);
6601
6657
  const severity = note.severity ?? "bug";
6602
6658
  const status = note.status ?? "open";
@@ -6930,7 +6986,96 @@ function NoteItem({
6930
6986
  style: { borderColor: "var(--qa-success)" }
6931
6987
  }
6932
6988
  )
6933
- ] })
6989
+ ] }),
6990
+ (status === "fixed" || note.followUp) && /* @__PURE__ */ jsx(
6991
+ "div",
6992
+ {
6993
+ className: "qa-mt-2 qa-rounded-lg qa-border qa-border-subtle qa-bg-2 qa-p-2",
6994
+ "data-qa-followup": "true",
6995
+ children: editingFollowUp ? /* @__PURE__ */ jsxs(Fragment, { children: [
6996
+ /* @__PURE__ */ jsx("label", { className: "qa-text-10 qa-text-lo", htmlFor: `qa-fu-${note.id}`, children: t("followup_label") }),
6997
+ /* @__PURE__ */ jsx(
6998
+ "textarea",
6999
+ {
7000
+ id: `qa-fu-${note.id}`,
7001
+ value: followUpDraft,
7002
+ onChange: (e) => setFollowUpDraft(e.target.value),
7003
+ placeholder: t("followup_placeholder"),
7004
+ rows: 3,
7005
+ className: "qa-mt-1 qa-w-full qa-rounded-lg qa-border qa-border-subtle qa-bg-1 qa-p-2 qa-text-xs qa-text-hi",
7006
+ style: { outline: "none", resize: "vertical" }
7007
+ }
7008
+ ),
7009
+ /* @__PURE__ */ jsx("p", { className: "qa-mt-1 qa-text-10 qa-text-lo", children: t("followup_hint") }),
7010
+ /* @__PURE__ */ jsxs("div", { className: "qa-mt-1.5 qa-flex qa-gap-2", children: [
7011
+ /* @__PURE__ */ jsxs(
7012
+ "button",
7013
+ {
7014
+ type: "button",
7015
+ onClick: () => {
7016
+ void updateNote(note.id, { followUp: followUpDraft });
7017
+ setEditingFollowUp(false);
7018
+ },
7019
+ className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-rounded-lg qa-bg-accent qa-px-2 qa-py-1 qa-text-11 qa-font-semibold",
7020
+ style: { border: "none", cursor: "pointer" },
7021
+ children: [
7022
+ /* @__PURE__ */ jsx(Icon, { name: "Check", size: 12 }),
7023
+ t("save")
7024
+ ]
7025
+ }
7026
+ ),
7027
+ /* @__PURE__ */ jsx(
7028
+ "button",
7029
+ {
7030
+ type: "button",
7031
+ onClick: () => {
7032
+ setFollowUpDraft(note.followUp ?? "");
7033
+ setEditingFollowUp(false);
7034
+ },
7035
+ className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-11 qa-text-mid",
7036
+ style: { background: "transparent", cursor: "pointer" },
7037
+ children: t("cancel")
7038
+ }
7039
+ )
7040
+ ] })
7041
+ ] }) : note.followUp ? /* @__PURE__ */ jsxs(Fragment, { children: [
7042
+ /* @__PURE__ */ jsxs("div", { className: "qa-flex qa-items-center qa-gap-1", children: [
7043
+ /* @__PURE__ */ jsx(Icon, { name: "RotateCcw", size: 11, className: "qa-shrink-0 qa-text-warn" }),
7044
+ /* @__PURE__ */ jsx("span", { className: "qa-text-10 qa-text-warn", children: t("followup_label") }),
7045
+ /* @__PURE__ */ jsx(
7046
+ "button",
7047
+ {
7048
+ type: "button",
7049
+ onClick: () => {
7050
+ setFollowUpDraft(note.followUp ?? "");
7051
+ setEditingFollowUp(true);
7052
+ },
7053
+ "aria-label": t("edit"),
7054
+ className: "qa-tap-icon qa-ms-auto qa-text-lo",
7055
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7056
+ children: /* @__PURE__ */ jsx(Icon, { name: "Pencil", size: 11 })
7057
+ }
7058
+ )
7059
+ ] }),
7060
+ /* @__PURE__ */ jsx("p", { className: "qa-mt-1 qa-whitespace-pre-wrap qa-break-words qa-text-xs qa-text-hi", children: note.followUp })
7061
+ ] }) : /* @__PURE__ */ jsxs(
7062
+ "button",
7063
+ {
7064
+ type: "button",
7065
+ onClick: () => {
7066
+ setFollowUpDraft("");
7067
+ setEditingFollowUp(true);
7068
+ },
7069
+ className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-text-11 qa-font-medium qa-text-accent",
7070
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7071
+ children: [
7072
+ /* @__PURE__ */ jsx(Icon, { name: "Plus", size: 12 }),
7073
+ t("followup_add")
7074
+ ]
7075
+ }
7076
+ )
7077
+ }
7078
+ )
6934
7079
  ] })
6935
7080
  ]
6936
7081
  }
@@ -7163,8 +7308,11 @@ function FilterChip({ chip }) {
7163
7308
  );
7164
7309
  }
7165
7310
  function NoteFilterBar() {
7166
- const { filter, setFilter, noteCounts, notes, t } = useQa();
7311
+ const { filter, setFilter, noteCounts, notes, deleteNotes, t } = useQa();
7312
+ const [confirmPrune, setConfirmPrune] = useState(false);
7167
7313
  if (notes.length < 3) return null;
7314
+ const others = notes.filter((n) => (n.status ?? "open") !== "fixed");
7315
+ const showPrune = filter.status === "fixed" && noteCounts.fixed > 0 && others.length > 0;
7168
7316
  const sev = (key) => () => setFilter({ severity: filter.severity === key ? "all" : key });
7169
7317
  const stat = (key) => () => setFilter({ status: filter.status === key ? "all" : key });
7170
7318
  const chips = [
@@ -7269,7 +7417,48 @@ function NoteFilterBar() {
7269
7417
  ]
7270
7418
  }
7271
7419
  ),
7272
- /* @__PURE__ */ jsx("div", { className: "qa-flex qa-flex-wrap qa-gap-1", children: chips.map((chip) => /* @__PURE__ */ jsx(FilterChip, { chip }, chip.key)) })
7420
+ /* @__PURE__ */ jsx("div", { className: "qa-flex qa-flex-wrap qa-gap-1", children: chips.map((chip) => /* @__PURE__ */ jsx(FilterChip, { chip }, chip.key)) }),
7421
+ showPrune && /* @__PURE__ */ jsx("div", { className: "qa-text-11", "data-qa-prune": "true", children: confirmPrune ? /* @__PURE__ */ jsxs("span", { className: "qa-text-mid", children: [
7422
+ t("keep_only_retest_q", { n: others.length }),
7423
+ " ",
7424
+ /* @__PURE__ */ jsx(
7425
+ "button",
7426
+ {
7427
+ type: "button",
7428
+ onClick: () => {
7429
+ void deleteNotes(others.map((n) => n.id));
7430
+ setConfirmPrune(false);
7431
+ },
7432
+ className: "qa-font-semibold qa-text-danger qa-tap",
7433
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7434
+ children: t("yes")
7435
+ }
7436
+ ),
7437
+ " / ",
7438
+ /* @__PURE__ */ jsx(
7439
+ "button",
7440
+ {
7441
+ type: "button",
7442
+ onClick: () => setConfirmPrune(false),
7443
+ className: "qa-text-accent qa-tap",
7444
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7445
+ children: t("no")
7446
+ }
7447
+ )
7448
+ ] }) : /* @__PURE__ */ jsxs(
7449
+ "button",
7450
+ {
7451
+ type: "button",
7452
+ onClick: () => setConfirmPrune(true),
7453
+ title: t("keep_only_retest_hint"),
7454
+ className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-text-lo",
7455
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7456
+ children: [
7457
+ /* @__PURE__ */ jsx(Icon, { name: "Trash", size: 11 }),
7458
+ t("keep_only_retest", { n: noteCounts.fixed })
7459
+ ]
7460
+ }
7461
+ ) })
7273
7462
  ] });
7274
7463
  }
7275
7464
  function Section({
@@ -7805,249 +7994,9 @@ function CredentialsSection() {
7805
7994
  })
7806
7995
  ] });
7807
7996
  }
7808
- var keyOf = (id, path) => `${id}::${path}`;
7809
- var DEFAULT_LANE_COLOR = "#4D9CFF";
7810
- function Lane({
7811
- group,
7812
- checked,
7813
- toggle,
7814
- pick: pick2,
7815
- onWalkFrom
7816
- }) {
7817
- const { lang, t, guideFailed, evidenceByStep } = useQa();
7818
- const { id, color = DEFAULT_LANE_COLOR, steps: steps2 } = group;
7819
- const done = steps2.filter((s) => checked.has(keyOf(id, s.path))).length;
7820
- const pct = steps2.length > 0 ? Math.round(done / steps2.length * 100) : 0;
7821
- const uncoveredRedCount = steps2.filter(
7822
- (s) => s.risk === "red" && !checked.has(keyOf(id, s.path))
7823
- ).length;
7824
- return /* @__PURE__ */ jsxs("div", { className: "qa-rounded-xl qa-border qa-border-subtle qa-bg-2 qa-p-3 qa-elev-1", children: [
7825
- /* @__PURE__ */ jsxs("div", { className: "qa-mb-2 qa-flex qa-items-center qa-gap-2", children: [
7826
- /* @__PURE__ */ jsx(
7827
- "span",
7828
- {
7829
- className: "qa-h-2.5 qa-w-2.5 qa-rounded-full",
7830
- style: { background: color }
7831
- }
7832
- ),
7833
- /* @__PURE__ */ jsx("span", { className: "qa-text-sm qa-font-bold qa-text-hi", children: pick2(group.role) }),
7834
- /* @__PURE__ */ jsxs("span", { className: "qa-ms-auto qa-text-11 qa-font-medium qa-text-slate-400", children: [
7835
- done,
7836
- "/",
7837
- steps2.length
7838
- ] }),
7839
- uncoveredRedCount > 0 && /* @__PURE__ */ jsx(
7840
- "span",
7841
- {
7842
- className: "qa-bg-danger-tint qa-text-danger qa-rounded qa-px-1 qa-text-10 qa-font-medium",
7843
- title: lang === "ar" ? `${uncoveredRedCount} \u0645\u0646\u0637\u0642\u0629 \u062D\u0645\u0631\u0627\u0621 \u063A\u064A\u0631 \u0645\u063A\u0637\u0627\u0629` : `${uncoveredRedCount} uncovered red zone(s)`,
7844
- children: lang === "ar" ? `\u0623\u062D\u0645\u0631: ${uncoveredRedCount}` : `red: ${uncoveredRedCount}`
7845
- }
7846
- )
7847
- ] }),
7848
- /* @__PURE__ */ jsx(
7849
- "div",
7850
- {
7851
- className: "qa-mb-3 qa-h-1.5 qa-overflow-hidden qa-rounded-full",
7852
- style: { background: `${color}22` },
7853
- children: /* @__PURE__ */ jsx(
7854
- "div",
7855
- {
7856
- className: "qa-h-full qa-rounded-full qa-transition-all",
7857
- style: { width: `${pct}%`, background: color }
7858
- }
7859
- )
7860
- }
7861
- ),
7862
- /* @__PURE__ */ jsxs("ol", { className: "qa-relative qa-ms-1.5", children: [
7863
- /* @__PURE__ */ jsx(
7864
- "span",
7865
- {
7866
- className: "qa-absolute qa-top-1 qa-bottom-0 qa-w-px",
7867
- style: { insetInlineStart: "7px", background: `${color}40`, bottom: "4px" }
7868
- }
7869
- ),
7870
- steps2.map((s, i) => {
7871
- const k = keyOf(id, s.path);
7872
- const on = checked.has(k);
7873
- const failed = guideFailed.has(k);
7874
- const evidence = evidenceByStep.get(k);
7875
- const evidenceCount = evidence ? evidence.length : 0;
7876
- const riskColor = s.risk ? RISK_COLORS[s.risk] : RISK_COLORS.none;
7877
- const dotTitle = !s.risk ? lang === "ar" ? "\u0644\u0645 \u064A\u062A\u0645 \u062A\u0642\u064A\u064A\u0645 \u0627\u0644\u0645\u062E\u0627\u0637\u0631 \u0628\u0639\u062F" : "not graded yet" : s.riskWhy ?? s.risk;
7878
- return /* @__PURE__ */ jsxs("li", { className: "qa-relative qa-mb-2 qa-last-mb-0", children: [
7879
- /* @__PURE__ */ jsx(
7880
- "button",
7881
- {
7882
- onClick: () => onWalkFrom(k),
7883
- title: t("walk_take_me", { path: s.path }),
7884
- "aria-label": t("walk_take_me", { path: s.path }),
7885
- className: "qa-tap-icon qa-absolute qa-rounded-lg qa-text-mid qa-hover-text-slate-600",
7886
- style: {
7887
- top: 0,
7888
- insetInlineEnd: 0,
7889
- background: "transparent",
7890
- border: "none",
7891
- cursor: "pointer",
7892
- zIndex: 2
7893
- },
7894
- children: /* @__PURE__ */ jsx(Icon, { name: "Play", size: 12 })
7895
- }
7896
- ),
7897
- /* @__PURE__ */ jsxs(
7898
- "button",
7899
- {
7900
- onClick: () => toggle(k),
7901
- className: `qa-flex qa-w-full qa-items-start qa-gap-2.5 qa-rounded-lg qa-p-1 qa-text-start qa-hover-bg-black-3${failed ? " qa-bg-danger-tint" : ""}`,
7902
- style: { background: failed ? void 0 : "transparent", border: "none", cursor: "pointer" },
7903
- children: [
7904
- /* @__PURE__ */ jsxs(
7905
- "span",
7906
- {
7907
- className: "qa-relative qa-z-1 qa-mt-0.5 qa-flex qa-h-4 qa-w-4 qa-shrink-0 qa-items-center qa-justify-center qa-rounded-full qa-border-2 qa-transition",
7908
- style: {
7909
- borderColor: failed ? "var(--qa-danger)" : color,
7910
- background: on ? color : failed ? "var(--qa-danger-tint)" : "var(--qa-surface-1)",
7911
- zIndex: 1
7912
- },
7913
- children: [
7914
- on && /* @__PURE__ */ jsx(Icon, { name: "Check", size: 10, strokeWidth: 3, className: "qa-text-white" }),
7915
- !on && failed && /* @__PURE__ */ jsx(Icon, { name: "AlertTriangle", size: 9, strokeWidth: 2.5, className: "qa-text-danger" })
7916
- ]
7917
- }
7918
- ),
7919
- /* @__PURE__ */ jsxs("span", { className: "qa-min-w-0", children: [
7920
- /* @__PURE__ */ jsxs("span", { className: "qa-flex qa-items-center qa-gap-1", children: [
7921
- /* @__PURE__ */ jsx(
7922
- "code",
7923
- {
7924
- className: "qa-rounded qa-px-1 qa-text-11 qa-font-semibold qa-dir-ltr qa-text-hi",
7925
- style: {
7926
- background: failed ? "var(--qa-danger-tint)" : `${color}14`,
7927
- textDecoration: on ? "line-through" : "none",
7928
- opacity: on ? 0.55 : 1
7929
- },
7930
- children: s.path
7931
- }
7932
- ),
7933
- /* @__PURE__ */ jsx(
7934
- "span",
7935
- {
7936
- className: "qa-inline-block qa-rounded-full qa-shrink-0",
7937
- style: {
7938
- width: "6px",
7939
- height: "6px",
7940
- background: riskColor,
7941
- flexShrink: 0
7942
- },
7943
- title: dotTitle
7944
- }
7945
- )
7946
- ] }),
7947
- /* @__PURE__ */ jsx(
7948
- "span",
7949
- {
7950
- className: "qa-mt-0.5 qa-block qa-text-11 qa-leading-relaxed qa-text-slate-500",
7951
- style: { opacity: on ? 0.5 : 1 },
7952
- children: pick2(s.what)
7953
- }
7954
- ),
7955
- evidenceCount > 0 && /* @__PURE__ */ jsx("span", { className: "qa-mt-1 qa-inline-flex qa-items-center qa-rounded qa-bg-accent-tint qa-text-accent qa-px-1 qa-text-10 qa-font-medium", children: t("evidence_n", { n: evidenceCount }) }),
7956
- evidenceCount === 0 && on && /* @__PURE__ */ jsx("span", { className: "qa-mt-1 qa-inline-flex qa-items-center qa-rounded qa-bg-warn-tint qa-text-warn qa-px-1 qa-text-10 qa-font-medium", children: t("no_evidence") })
7957
- ] })
7958
- ]
7959
- }
7960
- )
7961
- ] }, `${k}-${i}`);
7962
- })
7963
- ] })
7964
- ] });
7965
- }
7966
- function GuideSection() {
7967
- const {
7968
- guideChecked,
7969
- toggleGuide,
7970
- t,
7971
- journey,
7972
- journeyIsGeneric,
7973
- pick: pick2,
7974
- lang,
7975
- startTestAlong,
7976
- startWalk,
7977
- testAlongSteps
7978
- } = useQa();
7979
- const all = journey.flatMap((g) => g.steps.map((s) => keyOf(g.id, s.path)));
7980
- const done = all.filter((k) => guideChecked.has(k)).length;
7981
- const pct = all.length > 0 ? Math.round(done / all.length * 100) : 0;
7982
- const coverage = computeCoverage(journey, guideChecked);
7983
- return /* @__PURE__ */ jsxs("div", { className: "qa-space-y-3", children: [
7984
- journeyIsGeneric && /* @__PURE__ */ jsx(
7985
- "p",
7986
- {
7987
- className: "qa-m-0 qa-rounded-xl qa-border qa-border-dashed qa-border-subtle qa-p-2 qa-text-10 qa-text-mid qa-leading-relaxed",
7988
- "data-qa-generic-plan": "true",
7989
- children: t("journey_generic")
7990
- }
7991
- ),
7992
- /* @__PURE__ */ jsxs("div", { className: "qa-rounded-xl qa-border qa-border-accent qa-bg-accent-tint qa-p-3 qa-elev-1", children: [
7993
- coverage.red.total > 0 && /* @__PURE__ */ jsxs("div", { className: "qa-mb-1 qa-flex qa-items-center qa-gap-1.5 qa-text-11", children: [
7994
- /* @__PURE__ */ jsx("span", { className: "qa-bg-danger-tint qa-text-danger qa-rounded qa-px-1 qa-font-bold", children: lang === "ar" ? "\u0623\u062D\u0645\u0631" : "RED" }),
7995
- /* @__PURE__ */ jsxs("span", { className: "qa-dir-ltr qa-font-semibold qa-text-hi", children: [
7996
- coverage.red.covered,
7997
- "/",
7998
- coverage.red.total,
7999
- " ",
8000
- lang === "ar" ? "\u0645\u063A\u0637\u0649" : "covered"
8001
- ] })
8002
- ] }),
8003
- /* @__PURE__ */ jsxs("div", { className: "qa-flex qa-items-center qa-justify-between qa-text-sm qa-font-semibold qa-text-hi", children: [
8004
- /* @__PURE__ */ jsx("span", { children: t("journey_title") }),
8005
- /* @__PURE__ */ jsxs("span", { className: "qa-dir-ltr", children: [
8006
- done,
8007
- "/",
8008
- all.length,
8009
- " \xB7 ",
8010
- pct,
8011
- "%"
8012
- ] })
8013
- ] }),
8014
- /* @__PURE__ */ jsx("div", { className: "qa-mt-2 qa-h-2 qa-overflow-hidden qa-rounded-full qa-bg-3", children: /* @__PURE__ */ jsx(
8015
- "div",
8016
- {
8017
- className: "qa-h-full qa-rounded-full qa-bg-accent qa-transition-all",
8018
- style: { width: `${pct}%` }
8019
- }
8020
- ) }),
8021
- /* @__PURE__ */ jsx("div", { className: "qa-mt-2 qa-flex qa-justify-end", children: /* @__PURE__ */ jsxs(
8022
- "button",
8023
- {
8024
- type: "button",
8025
- onClick: startTestAlong,
8026
- disabled: all.length === 0,
8027
- className: "qa-tap qa-inline-flex qa-items-center qa-gap-1.5 qa-rounded-full qa-bg-accent qa-border-0 qa-cursor-pointer qa-px-3 qa-py-1 qa-text-xs qa-font-semibold qa-focus-ring",
8028
- children: [
8029
- /* @__PURE__ */ jsx(Icon, { name: "Play", size: 13 }),
8030
- t("start_walkthrough")
8031
- ]
8032
- }
8033
- ) })
8034
- ] }),
8035
- journey.map((g) => /* @__PURE__ */ jsx(
8036
- Lane,
8037
- {
8038
- group: g,
8039
- checked: guideChecked,
8040
- toggle: toggleGuide,
8041
- onWalkFrom: (key) => {
8042
- const at = testAlongSteps.findIndex((step) => step.key === key);
8043
- startWalk("plan", at === -1 ? 0 : at);
8044
- },
8045
- pick: pick2
8046
- },
8047
- g.id
8048
- ))
8049
- ] });
8050
- }
7997
+
7998
+ // src/lib/features.ts
7999
+ var GUIDE_TAB_ENABLED = false;
8051
8000
  function isCoarsePointer() {
8052
8001
  if (typeof window === "undefined") return false;
8053
8002
  try {
@@ -8071,11 +8020,15 @@ function useCoarsePointer() {
8071
8020
  }, []);
8072
8021
  return coarse;
8073
8022
  }
8074
- var TABS = [
8023
+ var ALL_TABS = [
8075
8024
  { key: "notes", labelKey: "tab_notes", icon: "StickyNote" },
8076
8025
  { key: "logins", labelKey: "tab_logins", icon: "KeyRound" },
8077
8026
  { key: "guide", labelKey: "tab_guide", icon: "Map" }
8078
8027
  ];
8028
+ var TABS = ALL_TABS.filter((tab) => tab.key !== "guide" || GUIDE_TAB_ENABLED);
8029
+ function visibleTab(tab) {
8030
+ return tab === "guide" && !GUIDE_TAB_ENABLED ? "notes" : tab;
8031
+ }
8079
8032
  function todayName() {
8080
8033
  return `qa-notes-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
8081
8034
  }
@@ -8110,7 +8063,7 @@ var PANEL_TRANSITION_WITH_LIFT = "opacity 200ms cubic-bezier(0.4,0,0.2,1), trans
8110
8063
  function QaPanel() {
8111
8064
  const {
8112
8065
  isOpen,
8113
- activeTab,
8066
+ activeTab: storedTab,
8114
8067
  setActiveTab,
8115
8068
  notes,
8116
8069
  exportZip,
@@ -8141,6 +8094,7 @@ function QaPanel() {
8141
8094
  panelCollapsed,
8142
8095
  setPanelCollapsed
8143
8096
  } = useQa();
8097
+ const activeTab = visibleTab(storedTab);
8144
8098
  const [confirmClear, setConfirmClear] = useState(false);
8145
8099
  const [naming, setNaming] = useState(false);
8146
8100
  const [filename, setFilename] = useState("");
@@ -8508,7 +8462,7 @@ function QaPanel() {
8508
8462
  ) })
8509
8463
  ] }),
8510
8464
  !simpleMode && activeTab === "logins" && /* @__PURE__ */ jsx(CredentialsSection, {}),
8511
- !simpleMode && activeTab === "guide" && /* @__PURE__ */ jsx(GuideSection, {})
8465
+ !simpleMode && GUIDE_TAB_ENABLED
8512
8466
  ] })
8513
8467
  ] }),
8514
8468
  settingsOpen && /* @__PURE__ */ jsx(SettingsSheet, { onClose: () => setSettingsOpen(false) }),
@@ -10365,5 +10319,5 @@ function Qapture({ config }) {
10365
10319
  }
10366
10320
 
10367
10321
  export { Qapture, deleteQaDatabase, initQaStudio };
10368
- //# sourceMappingURL=chunk-FVQKCQC3.js.map
10369
- //# sourceMappingURL=chunk-FVQKCQC3.js.map
10322
+ //# sourceMappingURL=chunk-2JX7YU2P.js.map
10323
+ //# sourceMappingURL=chunk-2JX7YU2P.js.map