qapture2 0.7.6 → 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.
@@ -1762,10 +1762,10 @@ var STR = {
1762
1762
  copied: "Copied",
1763
1763
  copy_failed: "Copy failed",
1764
1764
  copy_prompt: "Copy as agent prompt",
1765
- severity_label: "Severity",
1765
+ severity_label: "What is this?",
1766
1766
  sev_bug: "Bug",
1767
- sev_question: "Question",
1768
- sev_polish: "Polish",
1767
+ sev_design: "Design",
1768
+ sev_enhance: "Enhance",
1769
1769
  status_open: "Open",
1770
1770
  status_verified: "Verified",
1771
1771
  context_attached: "{n} runtime events attached",
@@ -1869,6 +1869,13 @@ var STR = {
1869
1869
  retest_not_found: "Can't find that spot on this page \u2014 open the right page first",
1870
1870
  before_label: "Before",
1871
1871
  after_label: "After",
1872
+ followup_label: "What happened this time",
1873
+ followup_add: "Say what happened",
1874
+ followup_placeholder: "What did it do this time, and how is that different from what you asked for?",
1875
+ followup_hint: "The first note stays as it is. This is added under it as round two.",
1876
+ keep_only_retest: "Keep only these {n}",
1877
+ keep_only_retest_q: "Delete the other {n}?",
1878
+ keep_only_retest_hint: "Delete every note that is not waiting to be re-tested",
1872
1879
  welcome_title: "Testing {brand}",
1873
1880
  welcome_capture: 'Click "Capture from page", then click whatever looks wrong.',
1874
1881
  welcome_say: "Say what you expected in your own words. A screenshot is taken for you.",
@@ -1960,10 +1967,10 @@ var STR = {
1960
1967
  copied: "\u062A\u0645 \u0627\u0644\u0646\u0633\u062E",
1961
1968
  copy_failed: "\u0641\u0634\u0644 \u0627\u0644\u0646\u0633\u062E",
1962
1969
  copy_prompt: "\u0646\u0633\u062E \u0643\u0645\u0648\u062C\u0651\u0647 \u0644\u0644\u0648\u0643\u064A\u0644",
1963
- severity_label: "\u0627\u0644\u0623\u0647\u0645\u064A\u0629",
1964
- sev_bug: "\u062E\u0644\u0644",
1965
- sev_question: "\u0633\u0624\u0627\u0644",
1966
- sev_polish: "\u062A\u062D\u0633\u064A\u0646",
1970
+ severity_label: "\u0645\u0627 \u0647\u0630\u0627\u061F",
1971
+ sev_bug: "Bug",
1972
+ sev_design: "Design",
1973
+ sev_enhance: "Enhance",
1967
1974
  status_open: "\u0645\u0641\u062A\u0648\u062D",
1968
1975
  status_verified: "\u062A\u0645 \u0627\u0644\u062A\u062D\u0642\u0642",
1969
1976
  context_attached: "{n} \u0645\u0646 \u0623\u062D\u062F\u0627\u062B \u0627\u0644\u062A\u0634\u063A\u064A\u0644 \u0645\u0631\u0641\u0642\u0629",
@@ -2067,6 +2074,13 @@ var STR = {
2067
2074
  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",
2068
2075
  before_label: "\u0642\u0628\u0644",
2069
2076
  after_label: "\u0628\u0639\u062F",
2077
+ followup_label: "\u0645\u0627\u0630\u0627 \u062D\u062F\u062B \u0647\u0630\u0647 \u0627\u0644\u0645\u0631\u0629",
2078
+ followup_add: "\u0627\u0643\u062A\u0628 \u0645\u0627\u0630\u0627 \u062D\u062F\u062B",
2079
+ 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",
2080
+ 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.",
2081
+ keep_only_retest: "\u0623\u0628\u0642\u0650 \u0647\u0630\u0647 \u0627\u0644\u0640 {n} \u0641\u0642\u0637",
2082
+ keep_only_retest_q: "\u062D\u0630\u0641 \u0627\u0644\u0640 {n} \u0627\u0644\u0623\u062E\u0631\u0649\u061F",
2083
+ 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",
2070
2084
  welcome_title: "\u0627\u062E\u062A\u0628\u0627\u0631 {brand}",
2071
2085
  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.",
2072
2086
  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.",
@@ -2111,18 +2125,6 @@ function pick(value, lang) {
2111
2125
  if (typeof value === "string") return value;
2112
2126
  return value[lang] ?? value.en ?? "";
2113
2127
  }
2114
-
2115
- // src/lib/coverage.ts
2116
- var RISK_COLORS = {
2117
- red: "#FF6B6B",
2118
- // --qa-danger
2119
- amber: "#FBBF24",
2120
- // --qa-warn
2121
- green: "#34D399",
2122
- // --qa-success
2123
- none: "#5B616B"
2124
- // --qa-neutral
2125
- };
2126
2128
  function computeCoverage(journey, guideChecked, skipped) {
2127
2129
  const red = { total: 0, covered: 0 };
2128
2130
  const amber = { total: 0, covered: 0 };
@@ -2976,16 +2978,19 @@ function clipToPaintedArea(el, rect) {
2976
2978
  let top = rect.top;
2977
2979
  let right = rect.left + rect.width;
2978
2980
  let bottom = rect.top + rect.height;
2981
+ const doc = el.ownerDocument;
2982
+ const root2 = doc ? doc.documentElement : null;
2983
+ const body = doc ? doc.body : null;
2984
+ const rootStyle = root2 ? safeComputedStyle(root2) : null;
2985
+ const bodyStyle = body ? safeComputedStyle(body) : null;
2979
2986
  let node = el.parentElement;
2980
- while (node) {
2981
- let style;
2982
- try {
2983
- style = getComputedStyle(node);
2984
- } catch {
2985
- break;
2986
- }
2987
- const clipsX = style.overflowX !== "visible";
2988
- const clipsY = style.overflowY !== "visible";
2987
+ while (node && node !== root2) {
2988
+ const style = safeComputedStyle(node);
2989
+ if (!style) break;
2990
+ const propagatedX = node === body && rootStyle?.overflowX === "visible";
2991
+ const propagatedY = node === body && rootStyle?.overflowY === "visible";
2992
+ const clipsX = !propagatedX && style.overflowX !== "visible";
2993
+ const clipsY = !propagatedY && style.overflowY !== "visible";
2989
2994
  if (clipsX || clipsY) {
2990
2995
  const b = node.getBoundingClientRect();
2991
2996
  const padLeft = b.left + node.clientLeft;
@@ -3001,9 +3006,33 @@ function clipToPaintedArea(el, rect) {
3001
3006
  }
3002
3007
  node = node.parentElement;
3003
3008
  }
3009
+ const vpX = usedViewportOverflow(rootStyle?.overflowX, bodyStyle?.overflowX);
3010
+ const vpY = usedViewportOverflow(rootStyle?.overflowY, bodyStyle?.overflowY);
3011
+ if (vpX === "hidden" || vpX === "clip" || vpY === "hidden" || vpY === "clip") {
3012
+ const { vw, vh } = viewportSize();
3013
+ if (vpX === "hidden" || vpX === "clip") {
3014
+ left = Math.max(left, 0);
3015
+ right = Math.min(right, vw);
3016
+ }
3017
+ if (vpY === "hidden" || vpY === "clip") {
3018
+ top = Math.max(top, 0);
3019
+ bottom = Math.min(bottom, vh);
3020
+ }
3021
+ }
3004
3022
  if (right - left < 2 || bottom - top < 2) return rect;
3005
3023
  return { left, top, width: right - left, height: bottom - top };
3006
3024
  }
3025
+ function safeComputedStyle(node) {
3026
+ try {
3027
+ return getComputedStyle(node);
3028
+ } catch {
3029
+ return null;
3030
+ }
3031
+ }
3032
+ function usedViewportOverflow(rootValue, bodyValue) {
3033
+ if (rootValue && rootValue !== "visible") return rootValue;
3034
+ return bodyValue || "visible";
3035
+ }
3007
3036
  function intersectViewport(rect) {
3008
3037
  const { vw, vh } = viewportSize();
3009
3038
  const left = Math.max(0, rect.left);
@@ -3198,6 +3227,16 @@ function noteToMarkdown(note, opts) {
3198
3227
  }
3199
3228
  lines.push("");
3200
3229
  lines.push(oneLine(note.description) ? note.description.trim() : "_(no description)_");
3230
+ if (note.followUp && oneLine(note.followUp)) {
3231
+ lines.push("");
3232
+ lines.push(
3233
+ "> **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."
3234
+ );
3235
+ lines.push("");
3236
+ lines.push(`**What happened this time**${note.followUpAt ? ` (${oneLine(note.followUpAt)})` : ""}`);
3237
+ lines.push("");
3238
+ lines.push(note.followUp.trim());
3239
+ }
3201
3240
  const recordedSteps = note.context?.steps ?? [];
3202
3241
  if (recordedSteps.length) {
3203
3242
  const t0 = Date.parse(note.timestamp) || recordedSteps[recordedSteps.length - 1].t;
@@ -3284,18 +3323,19 @@ function fmtPct(n, d) {
3284
3323
  }
3285
3324
  function summariseSession(notes, stamp) {
3286
3325
  if (!notes.length) return "No points captured.";
3287
- let bugs = 0, questions = 0, polish = 0, verified = 0, awaiting = 0;
3326
+ let bugs = 0, enhancements = 0, designs = 0, verified = 0, awaiting = 0, secondRound = 0;
3288
3327
  const pages = /* @__PURE__ */ new Set();
3289
3328
  let earliest = Number.POSITIVE_INFINITY;
3290
3329
  let latest = 0;
3291
3330
  for (const n of notes) {
3292
3331
  const sev = n.severity ?? "bug";
3293
3332
  if (sev === "bug") bugs++;
3294
- else if (sev === "question") questions++;
3295
- else polish++;
3333
+ else if (sev === "enhance") enhancements++;
3334
+ else designs++;
3296
3335
  const status = n.status ?? "open";
3297
3336
  if (status === "verified") verified++;
3298
3337
  else if (status === "fixed") awaiting++;
3338
+ if (n.followUp && n.followUp.trim()) secondRound++;
3299
3339
  pages.add((n.route || "/").split("?")[0]);
3300
3340
  const t = Date.parse(n.timestamp);
3301
3341
  if (Number.isFinite(t)) {
@@ -3307,10 +3347,11 @@ function summariseSession(notes, stamp) {
3307
3347
  `${notes.length} point${notes.length === 1 ? "" : "s"}`,
3308
3348
  `${bugs} bug${bugs === 1 ? "" : "s"}`
3309
3349
  ];
3310
- if (questions) parts.push(`${questions} question${questions === 1 ? "" : "s"}`);
3311
- if (polish) parts.push(`${polish} polish`);
3350
+ if (enhancements) parts.push(`${enhancements} enhancement${enhancements === 1 ? "" : "s"}`);
3351
+ if (designs) parts.push(`${designs} design`);
3312
3352
  if (verified) parts.push(`${verified} verified`);
3313
3353
  if (awaiting) parts.push(`${awaiting} awaiting re-test`);
3354
+ if (secondRound) parts.push(`${secondRound} back for a second round`);
3314
3355
  parts.push(`${pages.size} page${pages.size === 1 ? "" : "s"}`);
3315
3356
  if (Number.isFinite(earliest) && latest > earliest) {
3316
3357
  const minutes = Math.max(1, Math.round((latest - earliest) / 6e4));
@@ -3339,6 +3380,45 @@ Exported: ${stamp}
3339
3380
  Points: ${noteCount}
3340
3381
 
3341
3382
  **Session:** ${summariseSession(notes, stamp)}`
3383
+ );
3384
+ sections.push(
3385
+ `## What the tags mean
3386
+
3387
+ Every point below is tagged **Bug**, **Design** or **Enhance**. These are not priorities \u2014 they are three different kinds of work. Read the tag before you decide what to do.
3388
+
3389
+ ### Bug \u2014 something is broken
3390
+
3391
+ Find the **root cause**, not the symptom. Do not patch over it.
3392
+
3393
+ State the cause in one plain sentence before you change anything \u2014 e.g. "the date was wrong because the server sends UTC and the list formats in local time". **If you cannot write that sentence, you have not found the cause yet.** Keep looking. The real cause is often something small and silly; that is normal, and it is still the cause.
3394
+
3395
+ **The cause is not always in the code.** If the code does exactly what it was asked to do and the result is still wrong, then nothing is broken \u2014 what was *asked for* and what was *meant* did not match. That mismatch is the bug. Realign to what the owner meant and fix it there. If the note already says what they meant ("this isn't what I wanted, I wanted X"), they have told you \u2014 just do X. Do not stop to ask a question they have already answered.
3396
+
3397
+ ### Design \u2014 it works, but it looks or feels wrong
3398
+
3399
+ The functionality is fine and is not in question. Do not go hunting for a fault; there isn't one. Think properly about the interface and the experience \u2014 layout, spacing, hierarchy, wording, states, what the person is actually trying to do \u2014 and change how it looks and feels.
3400
+
3401
+ ### Enhance \u2014 a new idea, thought of while using the app
3402
+
3403
+ The tester wants something added or changed: a new section, a new feature, a different way of doing it.
3404
+
3405
+ **Plan it before you build it.** Read the surrounding code, work out what it touches and what it might break, and choose an approach that fits how the app is already built. Then build it. You do not need to stop and ask for approval \u2014 plan well, then go.
3406
+
3407
+ 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.
3408
+
3409
+ ### A point with a "round 2" on it
3410
+
3411
+ 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.
3412
+
3413
+ 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.
3414
+
3415
+ 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.
3416
+
3417
+ ### When the tag and the words disagree
3418
+
3419
+ 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.
3420
+
3421
+ And if a point is phrased as a question rather than a report, **answer it \u2014 do not change code on a maybe.**`
3342
3422
  );
3343
3423
  const stack = typeof p.stack === "string" && p.stack.trim() ? p.stack.trim() : "(not provided)";
3344
3424
  const runArr = toStrings(p.runCommands);
@@ -3934,12 +4014,12 @@ function campaignJsonText(allNotes) {
3934
4014
  function reportMarkdownText(allNotes) {
3935
4015
  if (!campaign) return "";
3936
4016
  const ordered = orderedKnownNotes(allNotes);
3937
- const counts = { bug: 0, question: 0, polish: 0, verified: 0 };
4017
+ const counts = { bug: 0, design: 0, enhance: 0, verified: 0 };
3938
4018
  for (const n of ordered) {
3939
4019
  const sev = n.severity ?? "bug";
3940
4020
  if (sev === "bug") counts.bug++;
3941
- else if (sev === "question") counts.question++;
3942
- else counts.polish++;
4021
+ else if (sev === "enhance") counts.enhance++;
4022
+ else counts.design++;
3943
4023
  if (n.status === "verified") counts.verified++;
3944
4024
  }
3945
4025
  const header = [
@@ -3948,7 +4028,7 @@ function reportMarkdownText(allNotes) {
3948
4028
  campaign.tester ? `Tester: ${campaign.tester} ` : "",
3949
4029
  `Started: ${campaign.startedAt} `,
3950
4030
  `Updated: ${(/* @__PURE__ */ new Date()).toISOString()} `,
3951
- `Points: ${ordered.length} (${counts.bug} bug, ${counts.question} question, ${counts.polish} polish \xB7 ${counts.verified} verified)`,
4031
+ `Points: ${ordered.length} (${counts.bug} bug, ${counts.design} design, ${counts.enhance} enhance \xB7 ${counts.verified} verified)`,
3952
4032
  "",
3953
4033
  getFsSyncEngine() === "download" ? "Saved by Qapture as the campaign was tested. Screenshots are in" : "Written live by Qapture as each point was saved. Screenshots are in",
3954
4034
  "`screenshots/`, one Markdown file per point in `notes/`.",
@@ -4300,6 +4380,12 @@ function QaProvider({
4300
4380
  for (const id of pendingIds) void idb.delete(id);
4301
4381
  storage.setJSON(PENDING_DELETE_KEY, []);
4302
4382
  }
4383
+ live = live.map((n) => {
4384
+ const legacy = n.severity;
4385
+ if (legacy === "question") return { ...n, severity: "enhance" };
4386
+ if (legacy === "polish") return { ...n, severity: "design" };
4387
+ return n;
4388
+ });
4303
4389
  const sorted = live.slice().sort(
4304
4390
  (a, b) => a.timestamp < b.timestamp ? 1 : -1
4305
4391
  );
@@ -4572,6 +4658,11 @@ function QaProvider({
4572
4658
  }
4573
4659
  if (patch.severity !== void 0) updated.severity = patch.severity;
4574
4660
  if (patch.status !== void 0) updated.status = patch.status;
4661
+ if (patch.followUp !== void 0) {
4662
+ const text = patch.followUp.trim();
4663
+ updated.followUp = text || void 0;
4664
+ updated.followUpAt = text ? nowIso() : void 0;
4665
+ }
4575
4666
  applyNotes((prev) => prev.map((n) => n.id === id ? updated : n));
4576
4667
  const persisted = await idb.put(updated);
4577
4668
  if (!persisted) {
@@ -4898,8 +4989,8 @@ function QaProvider({
4898
4989
  const counts = {
4899
4990
  all: notes.length,
4900
4991
  bug: 0,
4901
- question: 0,
4902
- polish: 0,
4992
+ design: 0,
4993
+ enhance: 0,
4903
4994
  open: 0,
4904
4995
  fixed: 0,
4905
4996
  verified: 0,
@@ -4908,8 +4999,8 @@ function QaProvider({
4908
4999
  for (const n of notes) {
4909
5000
  const sev = n.severity ?? "bug";
4910
5001
  if (sev === "bug") counts.bug++;
4911
- else if (sev === "question") counts.question++;
4912
- else counts.polish++;
5002
+ else if (sev === "enhance") counts.enhance++;
5003
+ else counts.design++;
4913
5004
  const status = n.status ?? "open";
4914
5005
  if (status === "verified") counts.verified++;
4915
5006
  else if (status === "fixed") counts.fixed++;
@@ -5298,7 +5389,11 @@ function QaProvider({
5298
5389
  pendingShare,
5299
5390
  sharePending,
5300
5391
  retestNote,
5301
- showWelcome,
5392
+ // The greeting explains how to capture. Once a note exists, the tester has
5393
+ // either done it or found their own way, so the line has nothing left to
5394
+ // say — take it away rather than making them dismiss it. (The stored flag
5395
+ // still governs a first visit; this only hides it early.)
5396
+ showWelcome: showWelcome && notes.length === 0,
5302
5397
  dismissWelcome,
5303
5398
  // v0.6
5304
5399
  panelSide,
@@ -5751,8 +5846,8 @@ function QaFab() {
5751
5846
  }
5752
5847
  var SEVERITIES = [
5753
5848
  { value: "bug", labelKey: "sev_bug", icon: "Bug" },
5754
- { value: "question", labelKey: "sev_question" },
5755
- { value: "polish", labelKey: "sev_polish" }
5849
+ { value: "enhance", labelKey: "sev_enhance" },
5850
+ { value: "design", labelKey: "sev_design" }
5756
5851
  ];
5757
5852
  function SeverityChipRow({
5758
5853
  value,
@@ -6497,13 +6592,13 @@ function KindBadge({
6497
6592
  }
6498
6593
  var SEVERITY_CLASS = {
6499
6594
  bug: "qa-bg-danger-tint qa-text-danger",
6500
- question: "qa-bg-warn-tint qa-text-warn",
6501
- polish: "qa-bg-accent-tint qa-text-accent"
6595
+ enhance: "qa-bg-warn-tint qa-text-warn",
6596
+ design: "qa-bg-accent-tint qa-text-accent"
6502
6597
  };
6503
6598
  var SEVERITY_LABEL_KEY = {
6504
6599
  bug: "sev_bug",
6505
- question: "sev_question",
6506
- polish: "sev_polish"
6600
+ enhance: "sev_enhance",
6601
+ design: "sev_design"
6507
6602
  };
6508
6603
  function SeverityChip({ severity, t }) {
6509
6604
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -6563,6 +6658,8 @@ function NoteItem({
6563
6658
  const thumbUrl = useObjectUrl(editing ? img ?? void 0 : note.screenshot);
6564
6659
  const afterUrl = useObjectUrl(note.afterScreenshot);
6565
6660
  const [retesting, setRetesting] = React.useState(false);
6661
+ const [editingFollowUp, setEditingFollowUp] = React.useState(false);
6662
+ const [followUpDraft, setFollowUpDraft] = React.useState(note.followUp ?? "");
6566
6663
  const [open, setOpen] = React.useState(false);
6567
6664
  const severity = note.severity ?? "bug";
6568
6665
  const status = note.status ?? "open";
@@ -6608,7 +6705,7 @@ function NoteItem({
6608
6705
  notify(t("copy_failed"), { tone: "error" });
6609
6706
  }
6610
6707
  };
6611
- const severityDot = severity === "bug" ? "var(--qa-danger)" : severity === "question" ? "var(--qa-warn)" : "var(--qa-accent)";
6708
+ const severityDot = severity === "bug" ? "var(--qa-danger)" : severity === "enhance" ? "var(--qa-warn)" : "var(--qa-accent)";
6612
6709
  if (dense && !open && !editing) {
6613
6710
  return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
6614
6711
  "button",
@@ -6896,7 +6993,96 @@ function NoteItem({
6896
6993
  style: { borderColor: "var(--qa-success)" }
6897
6994
  }
6898
6995
  )
6899
- ] })
6996
+ ] }),
6997
+ (status === "fixed" || note.followUp) && /* @__PURE__ */ jsxRuntime.jsx(
6998
+ "div",
6999
+ {
7000
+ className: "qa-mt-2 qa-rounded-lg qa-border qa-border-subtle qa-bg-2 qa-p-2",
7001
+ "data-qa-followup": "true",
7002
+ children: editingFollowUp ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7003
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "qa-text-10 qa-text-lo", htmlFor: `qa-fu-${note.id}`, children: t("followup_label") }),
7004
+ /* @__PURE__ */ jsxRuntime.jsx(
7005
+ "textarea",
7006
+ {
7007
+ id: `qa-fu-${note.id}`,
7008
+ value: followUpDraft,
7009
+ onChange: (e) => setFollowUpDraft(e.target.value),
7010
+ placeholder: t("followup_placeholder"),
7011
+ rows: 3,
7012
+ 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",
7013
+ style: { outline: "none", resize: "vertical" }
7014
+ }
7015
+ ),
7016
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-mt-1 qa-text-10 qa-text-lo", children: t("followup_hint") }),
7017
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-mt-1.5 qa-flex qa-gap-2", children: [
7018
+ /* @__PURE__ */ jsxRuntime.jsxs(
7019
+ "button",
7020
+ {
7021
+ type: "button",
7022
+ onClick: () => {
7023
+ void updateNote(note.id, { followUp: followUpDraft });
7024
+ setEditingFollowUp(false);
7025
+ },
7026
+ 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",
7027
+ style: { border: "none", cursor: "pointer" },
7028
+ children: [
7029
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Check", size: 12 }),
7030
+ t("save")
7031
+ ]
7032
+ }
7033
+ ),
7034
+ /* @__PURE__ */ jsxRuntime.jsx(
7035
+ "button",
7036
+ {
7037
+ type: "button",
7038
+ onClick: () => {
7039
+ setFollowUpDraft(note.followUp ?? "");
7040
+ setEditingFollowUp(false);
7041
+ },
7042
+ className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-11 qa-text-mid",
7043
+ style: { background: "transparent", cursor: "pointer" },
7044
+ children: t("cancel")
7045
+ }
7046
+ )
7047
+ ] })
7048
+ ] }) : note.followUp ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7049
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-flex qa-items-center qa-gap-1", children: [
7050
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "RotateCcw", size: 11, className: "qa-shrink-0 qa-text-warn" }),
7051
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-text-10 qa-text-warn", children: t("followup_label") }),
7052
+ /* @__PURE__ */ jsxRuntime.jsx(
7053
+ "button",
7054
+ {
7055
+ type: "button",
7056
+ onClick: () => {
7057
+ setFollowUpDraft(note.followUp ?? "");
7058
+ setEditingFollowUp(true);
7059
+ },
7060
+ "aria-label": t("edit"),
7061
+ className: "qa-tap-icon qa-ms-auto qa-text-lo",
7062
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7063
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Pencil", size: 11 })
7064
+ }
7065
+ )
7066
+ ] }),
7067
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-mt-1 qa-whitespace-pre-wrap qa-break-words qa-text-xs qa-text-hi", children: note.followUp })
7068
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
7069
+ "button",
7070
+ {
7071
+ type: "button",
7072
+ onClick: () => {
7073
+ setFollowUpDraft("");
7074
+ setEditingFollowUp(true);
7075
+ },
7076
+ className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-text-11 qa-font-medium qa-text-accent",
7077
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7078
+ children: [
7079
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Plus", size: 12 }),
7080
+ t("followup_add")
7081
+ ]
7082
+ }
7083
+ )
7084
+ }
7085
+ )
6900
7086
  ] })
6901
7087
  ]
6902
7088
  }
@@ -7129,8 +7315,11 @@ function FilterChip({ chip }) {
7129
7315
  );
7130
7316
  }
7131
7317
  function NoteFilterBar() {
7132
- const { filter, setFilter, noteCounts, notes, t } = useQa();
7318
+ const { filter, setFilter, noteCounts, notes, deleteNotes, t } = useQa();
7319
+ const [confirmPrune, setConfirmPrune] = React.useState(false);
7133
7320
  if (notes.length < 3) return null;
7321
+ const others = notes.filter((n) => (n.status ?? "open") !== "fixed");
7322
+ const showPrune = filter.status === "fixed" && noteCounts.fixed > 0 && others.length > 0;
7134
7323
  const sev = (key) => () => setFilter({ severity: filter.severity === key ? "all" : key });
7135
7324
  const stat = (key) => () => setFilter({ status: filter.status === key ? "all" : key });
7136
7325
  const chips = [
@@ -7151,22 +7340,22 @@ function NoteFilterBar() {
7151
7340
  onClick: sev("bug")
7152
7341
  },
7153
7342
  {
7154
- key: "question",
7155
- label: t("sev_question"),
7156
- count: noteCounts.question,
7157
- active: filter.severity === "question",
7158
- icon: "AlertTriangle",
7343
+ key: "enhance",
7344
+ label: t("sev_enhance"),
7345
+ count: noteCounts.enhance,
7346
+ active: filter.severity === "enhance",
7347
+ icon: "Plus",
7159
7348
  tone: "warn",
7160
- onClick: sev("question")
7349
+ onClick: sev("enhance")
7161
7350
  },
7162
7351
  {
7163
- key: "polish",
7164
- label: t("sev_polish"),
7165
- count: noteCounts.polish,
7166
- active: filter.severity === "polish",
7352
+ key: "design",
7353
+ label: t("sev_design"),
7354
+ count: noteCounts.design,
7355
+ active: filter.severity === "design",
7167
7356
  icon: "Pencil",
7168
7357
  tone: "accent",
7169
- onClick: sev("polish")
7358
+ onClick: sev("design")
7170
7359
  },
7171
7360
  {
7172
7361
  key: "open",
@@ -7235,7 +7424,48 @@ function NoteFilterBar() {
7235
7424
  ]
7236
7425
  }
7237
7426
  ),
7238
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-flex qa-flex-wrap qa-gap-1", children: chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(FilterChip, { chip }, chip.key)) })
7427
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-flex qa-flex-wrap qa-gap-1", children: chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(FilterChip, { chip }, chip.key)) }),
7428
+ showPrune && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-text-11", "data-qa-prune": "true", children: confirmPrune ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-text-mid", children: [
7429
+ t("keep_only_retest_q", { n: others.length }),
7430
+ " ",
7431
+ /* @__PURE__ */ jsxRuntime.jsx(
7432
+ "button",
7433
+ {
7434
+ type: "button",
7435
+ onClick: () => {
7436
+ void deleteNotes(others.map((n) => n.id));
7437
+ setConfirmPrune(false);
7438
+ },
7439
+ className: "qa-font-semibold qa-text-danger qa-tap",
7440
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7441
+ children: t("yes")
7442
+ }
7443
+ ),
7444
+ " / ",
7445
+ /* @__PURE__ */ jsxRuntime.jsx(
7446
+ "button",
7447
+ {
7448
+ type: "button",
7449
+ onClick: () => setConfirmPrune(false),
7450
+ className: "qa-text-accent qa-tap",
7451
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7452
+ children: t("no")
7453
+ }
7454
+ )
7455
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
7456
+ "button",
7457
+ {
7458
+ type: "button",
7459
+ onClick: () => setConfirmPrune(true),
7460
+ title: t("keep_only_retest_hint"),
7461
+ className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-text-lo",
7462
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7463
+ children: [
7464
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Trash", size: 11 }),
7465
+ t("keep_only_retest", { n: noteCounts.fixed })
7466
+ ]
7467
+ }
7468
+ ) })
7239
7469
  ] });
7240
7470
  }
7241
7471
  function Section({
@@ -7605,46 +7835,25 @@ function SettingsSheet({ onClose }) {
7605
7835
  );
7606
7836
  }
7607
7837
  function WelcomeCard() {
7608
- const { t, brand, dismissWelcome } = useQa();
7838
+ const { t, dismissWelcome } = useQa();
7609
7839
  return /* @__PURE__ */ jsxRuntime.jsxs(
7610
7840
  "div",
7611
7841
  {
7612
- className: "qa-rounded-xl qa-border qa-border-subtle qa-bg-1 qa-p-3 qa-space-y-2",
7842
+ className: "qa-flex qa-items-center qa-gap-2 qa-rounded-xl qa-border qa-border-subtle qa-bg-1 qa-px-3 qa-py-2",
7613
7843
  role: "note",
7614
7844
  children: [
7615
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "qa-m-0 qa-flex qa-items-center qa-gap-1.5 qa-text-sm qa-font-semibold qa-text-hi", children: [
7616
- /* @__PURE__ */ jsxRuntime.jsx(
7617
- "span",
7618
- {
7619
- "aria-hidden": "true",
7620
- className: "qa-shrink-0",
7621
- style: { width: 6, height: 6, background: "var(--qa-accent)" }
7622
- }
7623
- ),
7624
- t("welcome_title", { brand: brand.label })
7625
- ] }),
7626
- /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "qa-m-0 qa-space-y-1 qa-text-xs qa-text-mid", children: [
7627
- /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "qa-flex qa-items-start qa-gap-1.5", children: [
7628
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Crosshair", size: 12, className: "qa-shrink-0 qa-mt-1" }),
7629
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("welcome_capture") })
7630
- ] }),
7631
- /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "qa-flex qa-items-start qa-gap-1.5", children: [
7632
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Pencil", size: 12, className: "qa-shrink-0 qa-mt-1" }),
7633
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("welcome_say") })
7634
- ] }),
7635
- /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "qa-flex qa-items-start qa-gap-1.5", children: [
7636
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "CheckCircle2", size: 12, className: "qa-shrink-0 qa-mt-1" }),
7637
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("welcome_safe") })
7638
- ] })
7639
- ] }),
7845
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Crosshair", size: 13, className: "qa-shrink-0 qa-text-accent" }),
7846
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-flex-1 qa-text-xs qa-text-mid", children: t("welcome_capture") }),
7640
7847
  /* @__PURE__ */ jsxRuntime.jsx(
7641
7848
  "button",
7642
7849
  {
7643
7850
  type: "button",
7644
7851
  onClick: dismissWelcome,
7645
- className: "qa-tap qa-w-full qa-rounded-lg qa-bg-accent qa-px-3 qa-py-1.5 qa-text-xs qa-font-semibold",
7646
- style: { border: "none", cursor: "pointer" },
7647
- children: t("welcome_got_it")
7852
+ "aria-label": t("welcome_got_it"),
7853
+ title: t("welcome_got_it"),
7854
+ className: "qa-tap-icon qa-shrink-0 qa-rounded-full qa-text-mid qa-focus-ring",
7855
+ style: { background: "transparent", border: "none", cursor: "pointer", padding: 2 },
7856
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "X", size: 13 })
7648
7857
  }
7649
7858
  )
7650
7859
  ]
@@ -7792,249 +8001,9 @@ function CredentialsSection() {
7792
8001
  })
7793
8002
  ] });
7794
8003
  }
7795
- var keyOf = (id, path) => `${id}::${path}`;
7796
- var DEFAULT_LANE_COLOR = "#4D9CFF";
7797
- function Lane({
7798
- group,
7799
- checked,
7800
- toggle,
7801
- pick: pick2,
7802
- onWalkFrom
7803
- }) {
7804
- const { lang, t, guideFailed, evidenceByStep } = useQa();
7805
- const { id, color = DEFAULT_LANE_COLOR, steps: steps2 } = group;
7806
- const done = steps2.filter((s) => checked.has(keyOf(id, s.path))).length;
7807
- const pct = steps2.length > 0 ? Math.round(done / steps2.length * 100) : 0;
7808
- const uncoveredRedCount = steps2.filter(
7809
- (s) => s.risk === "red" && !checked.has(keyOf(id, s.path))
7810
- ).length;
7811
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-rounded-xl qa-border qa-border-subtle qa-bg-2 qa-p-3 qa-elev-1", children: [
7812
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-mb-2 qa-flex qa-items-center qa-gap-2", children: [
7813
- /* @__PURE__ */ jsxRuntime.jsx(
7814
- "span",
7815
- {
7816
- className: "qa-h-2.5 qa-w-2.5 qa-rounded-full",
7817
- style: { background: color }
7818
- }
7819
- ),
7820
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-text-sm qa-font-bold qa-text-hi", children: pick2(group.role) }),
7821
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-ms-auto qa-text-11 qa-font-medium qa-text-slate-400", children: [
7822
- done,
7823
- "/",
7824
- steps2.length
7825
- ] }),
7826
- uncoveredRedCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(
7827
- "span",
7828
- {
7829
- className: "qa-bg-danger-tint qa-text-danger qa-rounded qa-px-1 qa-text-10 qa-font-medium",
7830
- 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)`,
7831
- children: lang === "ar" ? `\u0623\u062D\u0645\u0631: ${uncoveredRedCount}` : `red: ${uncoveredRedCount}`
7832
- }
7833
- )
7834
- ] }),
7835
- /* @__PURE__ */ jsxRuntime.jsx(
7836
- "div",
7837
- {
7838
- className: "qa-mb-3 qa-h-1.5 qa-overflow-hidden qa-rounded-full",
7839
- style: { background: `${color}22` },
7840
- children: /* @__PURE__ */ jsxRuntime.jsx(
7841
- "div",
7842
- {
7843
- className: "qa-h-full qa-rounded-full qa-transition-all",
7844
- style: { width: `${pct}%`, background: color }
7845
- }
7846
- )
7847
- }
7848
- ),
7849
- /* @__PURE__ */ jsxRuntime.jsxs("ol", { className: "qa-relative qa-ms-1.5", children: [
7850
- /* @__PURE__ */ jsxRuntime.jsx(
7851
- "span",
7852
- {
7853
- className: "qa-absolute qa-top-1 qa-bottom-0 qa-w-px",
7854
- style: { insetInlineStart: "7px", background: `${color}40`, bottom: "4px" }
7855
- }
7856
- ),
7857
- steps2.map((s, i) => {
7858
- const k = keyOf(id, s.path);
7859
- const on = checked.has(k);
7860
- const failed = guideFailed.has(k);
7861
- const evidence = evidenceByStep.get(k);
7862
- const evidenceCount = evidence ? evidence.length : 0;
7863
- const riskColor = s.risk ? RISK_COLORS[s.risk] : RISK_COLORS.none;
7864
- 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;
7865
- return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "qa-relative qa-mb-2 qa-last-mb-0", children: [
7866
- /* @__PURE__ */ jsxRuntime.jsx(
7867
- "button",
7868
- {
7869
- onClick: () => onWalkFrom(k),
7870
- title: t("walk_take_me", { path: s.path }),
7871
- "aria-label": t("walk_take_me", { path: s.path }),
7872
- className: "qa-tap-icon qa-absolute qa-rounded-lg qa-text-mid qa-hover-text-slate-600",
7873
- style: {
7874
- top: 0,
7875
- insetInlineEnd: 0,
7876
- background: "transparent",
7877
- border: "none",
7878
- cursor: "pointer",
7879
- zIndex: 2
7880
- },
7881
- children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Play", size: 12 })
7882
- }
7883
- ),
7884
- /* @__PURE__ */ jsxRuntime.jsxs(
7885
- "button",
7886
- {
7887
- onClick: () => toggle(k),
7888
- 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" : ""}`,
7889
- style: { background: failed ? void 0 : "transparent", border: "none", cursor: "pointer" },
7890
- children: [
7891
- /* @__PURE__ */ jsxRuntime.jsxs(
7892
- "span",
7893
- {
7894
- 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",
7895
- style: {
7896
- borderColor: failed ? "var(--qa-danger)" : color,
7897
- background: on ? color : failed ? "var(--qa-danger-tint)" : "var(--qa-surface-1)",
7898
- zIndex: 1
7899
- },
7900
- children: [
7901
- on && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Check", size: 10, strokeWidth: 3, className: "qa-text-white" }),
7902
- !on && failed && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "AlertTriangle", size: 9, strokeWidth: 2.5, className: "qa-text-danger" })
7903
- ]
7904
- }
7905
- ),
7906
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-min-w-0", children: [
7907
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-flex qa-items-center qa-gap-1", children: [
7908
- /* @__PURE__ */ jsxRuntime.jsx(
7909
- "code",
7910
- {
7911
- className: "qa-rounded qa-px-1 qa-text-11 qa-font-semibold qa-dir-ltr qa-text-hi",
7912
- style: {
7913
- background: failed ? "var(--qa-danger-tint)" : `${color}14`,
7914
- textDecoration: on ? "line-through" : "none",
7915
- opacity: on ? 0.55 : 1
7916
- },
7917
- children: s.path
7918
- }
7919
- ),
7920
- /* @__PURE__ */ jsxRuntime.jsx(
7921
- "span",
7922
- {
7923
- className: "qa-inline-block qa-rounded-full qa-shrink-0",
7924
- style: {
7925
- width: "6px",
7926
- height: "6px",
7927
- background: riskColor,
7928
- flexShrink: 0
7929
- },
7930
- title: dotTitle
7931
- }
7932
- )
7933
- ] }),
7934
- /* @__PURE__ */ jsxRuntime.jsx(
7935
- "span",
7936
- {
7937
- className: "qa-mt-0.5 qa-block qa-text-11 qa-leading-relaxed qa-text-slate-500",
7938
- style: { opacity: on ? 0.5 : 1 },
7939
- children: pick2(s.what)
7940
- }
7941
- ),
7942
- evidenceCount > 0 && /* @__PURE__ */ jsxRuntime.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 }) }),
7943
- evidenceCount === 0 && on && /* @__PURE__ */ jsxRuntime.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") })
7944
- ] })
7945
- ]
7946
- }
7947
- )
7948
- ] }, `${k}-${i}`);
7949
- })
7950
- ] })
7951
- ] });
7952
- }
7953
- function GuideSection() {
7954
- const {
7955
- guideChecked,
7956
- toggleGuide,
7957
- t,
7958
- journey,
7959
- journeyIsGeneric,
7960
- pick: pick2,
7961
- lang,
7962
- startTestAlong,
7963
- startWalk,
7964
- testAlongSteps
7965
- } = useQa();
7966
- const all = journey.flatMap((g) => g.steps.map((s) => keyOf(g.id, s.path)));
7967
- const done = all.filter((k) => guideChecked.has(k)).length;
7968
- const pct = all.length > 0 ? Math.round(done / all.length * 100) : 0;
7969
- const coverage = computeCoverage(journey, guideChecked);
7970
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-space-y-3", children: [
7971
- journeyIsGeneric && /* @__PURE__ */ jsxRuntime.jsx(
7972
- "p",
7973
- {
7974
- 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",
7975
- "data-qa-generic-plan": "true",
7976
- children: t("journey_generic")
7977
- }
7978
- ),
7979
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-rounded-xl qa-border qa-border-accent qa-bg-accent-tint qa-p-3 qa-elev-1", children: [
7980
- coverage.red.total > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-mb-1 qa-flex qa-items-center qa-gap-1.5 qa-text-11", children: [
7981
- /* @__PURE__ */ jsxRuntime.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" }),
7982
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-dir-ltr qa-font-semibold qa-text-hi", children: [
7983
- coverage.red.covered,
7984
- "/",
7985
- coverage.red.total,
7986
- " ",
7987
- lang === "ar" ? "\u0645\u063A\u0637\u0649" : "covered"
7988
- ] })
7989
- ] }),
7990
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-flex qa-items-center qa-justify-between qa-text-sm qa-font-semibold qa-text-hi", children: [
7991
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("journey_title") }),
7992
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-dir-ltr", children: [
7993
- done,
7994
- "/",
7995
- all.length,
7996
- " \xB7 ",
7997
- pct,
7998
- "%"
7999
- ] })
8000
- ] }),
8001
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-mt-2 qa-h-2 qa-overflow-hidden qa-rounded-full qa-bg-3", children: /* @__PURE__ */ jsxRuntime.jsx(
8002
- "div",
8003
- {
8004
- className: "qa-h-full qa-rounded-full qa-bg-accent qa-transition-all",
8005
- style: { width: `${pct}%` }
8006
- }
8007
- ) }),
8008
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-mt-2 qa-flex qa-justify-end", children: /* @__PURE__ */ jsxRuntime.jsxs(
8009
- "button",
8010
- {
8011
- type: "button",
8012
- onClick: startTestAlong,
8013
- disabled: all.length === 0,
8014
- 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",
8015
- children: [
8016
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Play", size: 13 }),
8017
- t("start_walkthrough")
8018
- ]
8019
- }
8020
- ) })
8021
- ] }),
8022
- journey.map((g) => /* @__PURE__ */ jsxRuntime.jsx(
8023
- Lane,
8024
- {
8025
- group: g,
8026
- checked: guideChecked,
8027
- toggle: toggleGuide,
8028
- onWalkFrom: (key) => {
8029
- const at = testAlongSteps.findIndex((step) => step.key === key);
8030
- startWalk("plan", at === -1 ? 0 : at);
8031
- },
8032
- pick: pick2
8033
- },
8034
- g.id
8035
- ))
8036
- ] });
8037
- }
8004
+
8005
+ // src/lib/features.ts
8006
+ var GUIDE_TAB_ENABLED = false;
8038
8007
  function isCoarsePointer() {
8039
8008
  if (typeof window === "undefined") return false;
8040
8009
  try {
@@ -8058,11 +8027,15 @@ function useCoarsePointer() {
8058
8027
  }, []);
8059
8028
  return coarse;
8060
8029
  }
8061
- var TABS = [
8030
+ var ALL_TABS = [
8062
8031
  { key: "notes", labelKey: "tab_notes", icon: "StickyNote" },
8063
8032
  { key: "logins", labelKey: "tab_logins", icon: "KeyRound" },
8064
8033
  { key: "guide", labelKey: "tab_guide", icon: "Map" }
8065
8034
  ];
8035
+ var TABS = ALL_TABS.filter((tab) => tab.key !== "guide" || GUIDE_TAB_ENABLED);
8036
+ function visibleTab(tab) {
8037
+ return tab === "guide" && !GUIDE_TAB_ENABLED ? "notes" : tab;
8038
+ }
8066
8039
  function todayName() {
8067
8040
  return `qa-notes-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
8068
8041
  }
@@ -8097,7 +8070,7 @@ var PANEL_TRANSITION_WITH_LIFT = "opacity 200ms cubic-bezier(0.4,0,0.2,1), trans
8097
8070
  function QaPanel() {
8098
8071
  const {
8099
8072
  isOpen,
8100
- activeTab,
8073
+ activeTab: storedTab,
8101
8074
  setActiveTab,
8102
8075
  notes,
8103
8076
  exportZip,
@@ -8128,6 +8101,7 @@ function QaPanel() {
8128
8101
  panelCollapsed,
8129
8102
  setPanelCollapsed
8130
8103
  } = useQa();
8104
+ const activeTab = visibleTab(storedTab);
8131
8105
  const [confirmClear, setConfirmClear] = React.useState(false);
8132
8106
  const [naming, setNaming] = React.useState(false);
8133
8107
  const [filename, setFilename] = React.useState("");
@@ -8495,7 +8469,7 @@ function QaPanel() {
8495
8469
  ) })
8496
8470
  ] }),
8497
8471
  !simpleMode && activeTab === "logins" && /* @__PURE__ */ jsxRuntime.jsx(CredentialsSection, {}),
8498
- !simpleMode && activeTab === "guide" && /* @__PURE__ */ jsxRuntime.jsx(GuideSection, {})
8472
+ !simpleMode && GUIDE_TAB_ENABLED
8499
8473
  ] })
8500
8474
  ] }),
8501
8475
  settingsOpen && /* @__PURE__ */ jsxRuntime.jsx(SettingsSheet, { onClose: () => setSettingsOpen(false) }),
@@ -9111,16 +9085,16 @@ function unlockPageScroll() {
9111
9085
  var DRAG_THRESHOLD2 = 6;
9112
9086
  var TOUCH_DRAG_THRESHOLD = 12;
9113
9087
  var MIN_REGION_SIZE = 8;
9114
- var SEVERITIES2 = ["bug", "question", "polish"];
9088
+ var SEVERITIES2 = ["bug", "design", "enhance"];
9115
9089
  var SEVERITY_ICON = {
9116
9090
  bug: "Bug",
9117
- question: "AlertTriangle",
9118
- polish: "Pencil"
9091
+ design: "Pencil",
9092
+ enhance: "Plus"
9119
9093
  };
9120
9094
  var SEVERITY_LABEL_KEY2 = {
9121
9095
  bug: "sev_bug",
9122
- question: "sev_question",
9123
- polish: "sev_polish"
9096
+ design: "sev_design",
9097
+ enhance: "sev_enhance"
9124
9098
  };
9125
9099
  function clampRegionRect(rect) {
9126
9100
  const vw = typeof window !== "undefined" ? window.innerWidth : rect.left + rect.width;
@@ -10074,7 +10048,7 @@ function CaptureMode() {
10074
10048
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-text-11 qa-text-mid qa-me-1", children: t("severity_label") }),
10075
10049
  SEVERITIES2.map((sev) => {
10076
10050
  const active = severity === sev;
10077
- const toneClass = sev === "bug" ? "qa-bg-danger-tint qa-text-danger" : sev === "question" ? "qa-bg-warn-tint qa-text-warn" : "qa-bg-accent-tint qa-text-accent";
10051
+ const toneClass = sev === "bug" ? "qa-bg-danger-tint qa-text-danger" : sev === "enhance" ? "qa-bg-warn-tint qa-text-warn" : "qa-bg-accent-tint qa-text-accent";
10078
10052
  return /* @__PURE__ */ jsxRuntime.jsxs(
10079
10053
  "button",
10080
10054
  {
@@ -10354,5 +10328,5 @@ function Qapture({ config }) {
10354
10328
  exports.Qapture = Qapture;
10355
10329
  exports.deleteQaDatabase = deleteQaDatabase;
10356
10330
  exports.initQaStudio = initQaStudio;
10357
- //# sourceMappingURL=chunk-CNNNWLTX.cjs.map
10358
- //# sourceMappingURL=chunk-CNNNWLTX.cjs.map
10331
+ //# sourceMappingURL=chunk-4LMLW73U.cjs.map
10332
+ //# sourceMappingURL=chunk-4LMLW73U.cjs.map