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.
@@ -1755,10 +1755,10 @@ var STR = {
1755
1755
  copied: "Copied",
1756
1756
  copy_failed: "Copy failed",
1757
1757
  copy_prompt: "Copy as agent prompt",
1758
- severity_label: "Severity",
1758
+ severity_label: "What is this?",
1759
1759
  sev_bug: "Bug",
1760
- sev_question: "Question",
1761
- sev_polish: "Polish",
1760
+ sev_design: "Design",
1761
+ sev_enhance: "Enhance",
1762
1762
  status_open: "Open",
1763
1763
  status_verified: "Verified",
1764
1764
  context_attached: "{n} runtime events attached",
@@ -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.",
@@ -1953,10 +1960,10 @@ var STR = {
1953
1960
  copied: "\u062A\u0645 \u0627\u0644\u0646\u0633\u062E",
1954
1961
  copy_failed: "\u0641\u0634\u0644 \u0627\u0644\u0646\u0633\u062E",
1955
1962
  copy_prompt: "\u0646\u0633\u062E \u0643\u0645\u0648\u062C\u0651\u0647 \u0644\u0644\u0648\u0643\u064A\u0644",
1956
- severity_label: "\u0627\u0644\u0623\u0647\u0645\u064A\u0629",
1957
- sev_bug: "\u062E\u0644\u0644",
1958
- sev_question: "\u0633\u0624\u0627\u0644",
1959
- sev_polish: "\u062A\u062D\u0633\u064A\u0646",
1963
+ severity_label: "\u0645\u0627 \u0647\u0630\u0627\u061F",
1964
+ sev_bug: "Bug",
1965
+ sev_design: "Design",
1966
+ sev_enhance: "Enhance",
1960
1967
  status_open: "\u0645\u0641\u062A\u0648\u062D",
1961
1968
  status_verified: "\u062A\u0645 \u0627\u0644\u062A\u062D\u0642\u0642",
1962
1969
  context_attached: "{n} \u0645\u0646 \u0623\u062D\u062F\u0627\u062B \u0627\u0644\u062A\u0634\u063A\u064A\u0644 \u0645\u0631\u0641\u0642\u0629",
@@ -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,18 +3316,19 @@ 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, questions = 0, polish = 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;
3284
3323
  for (const n of notes) {
3285
3324
  const sev = n.severity ?? "bug";
3286
3325
  if (sev === "bug") bugs++;
3287
- else if (sev === "question") questions++;
3288
- else polish++;
3326
+ else if (sev === "enhance") enhancements++;
3327
+ else designs++;
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)) {
@@ -3300,10 +3340,11 @@ function summariseSession(notes, stamp) {
3300
3340
  `${notes.length} point${notes.length === 1 ? "" : "s"}`,
3301
3341
  `${bugs} bug${bugs === 1 ? "" : "s"}`
3302
3342
  ];
3303
- if (questions) parts.push(`${questions} question${questions === 1 ? "" : "s"}`);
3304
- if (polish) parts.push(`${polish} polish`);
3343
+ if (enhancements) parts.push(`${enhancements} enhancement${enhancements === 1 ? "" : "s"}`);
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));
@@ -3332,6 +3373,45 @@ Exported: ${stamp}
3332
3373
  Points: ${noteCount}
3333
3374
 
3334
3375
  **Session:** ${summariseSession(notes, stamp)}`
3376
+ );
3377
+ sections.push(
3378
+ `## What the tags mean
3379
+
3380
+ 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.
3381
+
3382
+ ### Bug \u2014 something is broken
3383
+
3384
+ Find the **root cause**, not the symptom. Do not patch over it.
3385
+
3386
+ 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.
3387
+
3388
+ **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.
3389
+
3390
+ ### Design \u2014 it works, but it looks or feels wrong
3391
+
3392
+ 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.
3393
+
3394
+ ### Enhance \u2014 a new idea, thought of while using the app
3395
+
3396
+ The tester wants something added or changed: a new section, a new feature, a different way of doing it.
3397
+
3398
+ **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.
3399
+
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.
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
+
3410
+ ### When the tag and the words disagree
3411
+
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.
3413
+
3414
+ And if a point is phrased as a question rather than a report, **answer it \u2014 do not change code on a maybe.**`
3335
3415
  );
3336
3416
  const stack = typeof p.stack === "string" && p.stack.trim() ? p.stack.trim() : "(not provided)";
3337
3417
  const runArr = toStrings(p.runCommands);
@@ -3927,12 +4007,12 @@ function campaignJsonText(allNotes) {
3927
4007
  function reportMarkdownText(allNotes) {
3928
4008
  if (!campaign) return "";
3929
4009
  const ordered = orderedKnownNotes(allNotes);
3930
- const counts = { bug: 0, question: 0, polish: 0, verified: 0 };
4010
+ const counts = { bug: 0, design: 0, enhance: 0, verified: 0 };
3931
4011
  for (const n of ordered) {
3932
4012
  const sev = n.severity ?? "bug";
3933
4013
  if (sev === "bug") counts.bug++;
3934
- else if (sev === "question") counts.question++;
3935
- else counts.polish++;
4014
+ else if (sev === "enhance") counts.enhance++;
4015
+ else counts.design++;
3936
4016
  if (n.status === "verified") counts.verified++;
3937
4017
  }
3938
4018
  const header = [
@@ -3941,7 +4021,7 @@ function reportMarkdownText(allNotes) {
3941
4021
  campaign.tester ? `Tester: ${campaign.tester} ` : "",
3942
4022
  `Started: ${campaign.startedAt} `,
3943
4023
  `Updated: ${(/* @__PURE__ */ new Date()).toISOString()} `,
3944
- `Points: ${ordered.length} (${counts.bug} bug, ${counts.question} question, ${counts.polish} polish \xB7 ${counts.verified} verified)`,
4024
+ `Points: ${ordered.length} (${counts.bug} bug, ${counts.design} design, ${counts.enhance} enhance \xB7 ${counts.verified} verified)`,
3945
4025
  "",
3946
4026
  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",
3947
4027
  "`screenshots/`, one Markdown file per point in `notes/`.",
@@ -4293,6 +4373,12 @@ function QaProvider({
4293
4373
  for (const id of pendingIds) void idb.delete(id);
4294
4374
  storage.setJSON(PENDING_DELETE_KEY, []);
4295
4375
  }
4376
+ live = live.map((n) => {
4377
+ const legacy = n.severity;
4378
+ if (legacy === "question") return { ...n, severity: "enhance" };
4379
+ if (legacy === "polish") return { ...n, severity: "design" };
4380
+ return n;
4381
+ });
4296
4382
  const sorted = live.slice().sort(
4297
4383
  (a, b) => a.timestamp < b.timestamp ? 1 : -1
4298
4384
  );
@@ -4565,6 +4651,11 @@ function QaProvider({
4565
4651
  }
4566
4652
  if (patch.severity !== void 0) updated.severity = patch.severity;
4567
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
+ }
4568
4659
  applyNotes((prev) => prev.map((n) => n.id === id ? updated : n));
4569
4660
  const persisted = await idb.put(updated);
4570
4661
  if (!persisted) {
@@ -4891,8 +4982,8 @@ function QaProvider({
4891
4982
  const counts = {
4892
4983
  all: notes.length,
4893
4984
  bug: 0,
4894
- question: 0,
4895
- polish: 0,
4985
+ design: 0,
4986
+ enhance: 0,
4896
4987
  open: 0,
4897
4988
  fixed: 0,
4898
4989
  verified: 0,
@@ -4901,8 +4992,8 @@ function QaProvider({
4901
4992
  for (const n of notes) {
4902
4993
  const sev = n.severity ?? "bug";
4903
4994
  if (sev === "bug") counts.bug++;
4904
- else if (sev === "question") counts.question++;
4905
- else counts.polish++;
4995
+ else if (sev === "enhance") counts.enhance++;
4996
+ else counts.design++;
4906
4997
  const status = n.status ?? "open";
4907
4998
  if (status === "verified") counts.verified++;
4908
4999
  else if (status === "fixed") counts.fixed++;
@@ -5291,7 +5382,11 @@ function QaProvider({
5291
5382
  pendingShare,
5292
5383
  sharePending,
5293
5384
  retestNote,
5294
- showWelcome,
5385
+ // The greeting explains how to capture. Once a note exists, the tester has
5386
+ // either done it or found their own way, so the line has nothing left to
5387
+ // say — take it away rather than making them dismiss it. (The stored flag
5388
+ // still governs a first visit; this only hides it early.)
5389
+ showWelcome: showWelcome && notes.length === 0,
5295
5390
  dismissWelcome,
5296
5391
  // v0.6
5297
5392
  panelSide,
@@ -5744,8 +5839,8 @@ function QaFab() {
5744
5839
  }
5745
5840
  var SEVERITIES = [
5746
5841
  { value: "bug", labelKey: "sev_bug", icon: "Bug" },
5747
- { value: "question", labelKey: "sev_question" },
5748
- { value: "polish", labelKey: "sev_polish" }
5842
+ { value: "enhance", labelKey: "sev_enhance" },
5843
+ { value: "design", labelKey: "sev_design" }
5749
5844
  ];
5750
5845
  function SeverityChipRow({
5751
5846
  value,
@@ -6490,13 +6585,13 @@ function KindBadge({
6490
6585
  }
6491
6586
  var SEVERITY_CLASS = {
6492
6587
  bug: "qa-bg-danger-tint qa-text-danger",
6493
- question: "qa-bg-warn-tint qa-text-warn",
6494
- polish: "qa-bg-accent-tint qa-text-accent"
6588
+ enhance: "qa-bg-warn-tint qa-text-warn",
6589
+ design: "qa-bg-accent-tint qa-text-accent"
6495
6590
  };
6496
6591
  var SEVERITY_LABEL_KEY = {
6497
6592
  bug: "sev_bug",
6498
- question: "sev_question",
6499
- polish: "sev_polish"
6593
+ enhance: "sev_enhance",
6594
+ design: "sev_design"
6500
6595
  };
6501
6596
  function SeverityChip({ severity, t }) {
6502
6597
  return /* @__PURE__ */ jsxs(
@@ -6556,6 +6651,8 @@ function NoteItem({
6556
6651
  const thumbUrl = useObjectUrl(editing ? img ?? void 0 : note.screenshot);
6557
6652
  const afterUrl = useObjectUrl(note.afterScreenshot);
6558
6653
  const [retesting, setRetesting] = useState(false);
6654
+ const [editingFollowUp, setEditingFollowUp] = useState(false);
6655
+ const [followUpDraft, setFollowUpDraft] = useState(note.followUp ?? "");
6559
6656
  const [open, setOpen] = useState(false);
6560
6657
  const severity = note.severity ?? "bug";
6561
6658
  const status = note.status ?? "open";
@@ -6601,7 +6698,7 @@ function NoteItem({
6601
6698
  notify(t("copy_failed"), { tone: "error" });
6602
6699
  }
6603
6700
  };
6604
- const severityDot = severity === "bug" ? "var(--qa-danger)" : severity === "question" ? "var(--qa-warn)" : "var(--qa-accent)";
6701
+ const severityDot = severity === "bug" ? "var(--qa-danger)" : severity === "enhance" ? "var(--qa-warn)" : "var(--qa-accent)";
6605
6702
  if (dense && !open && !editing) {
6606
6703
  return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
6607
6704
  "button",
@@ -6889,7 +6986,96 @@ function NoteItem({
6889
6986
  style: { borderColor: "var(--qa-success)" }
6890
6987
  }
6891
6988
  )
6892
- ] })
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
+ )
6893
7079
  ] })
6894
7080
  ]
6895
7081
  }
@@ -7122,8 +7308,11 @@ function FilterChip({ chip }) {
7122
7308
  );
7123
7309
  }
7124
7310
  function NoteFilterBar() {
7125
- const { filter, setFilter, noteCounts, notes, t } = useQa();
7311
+ const { filter, setFilter, noteCounts, notes, deleteNotes, t } = useQa();
7312
+ const [confirmPrune, setConfirmPrune] = useState(false);
7126
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;
7127
7316
  const sev = (key) => () => setFilter({ severity: filter.severity === key ? "all" : key });
7128
7317
  const stat = (key) => () => setFilter({ status: filter.status === key ? "all" : key });
7129
7318
  const chips = [
@@ -7144,22 +7333,22 @@ function NoteFilterBar() {
7144
7333
  onClick: sev("bug")
7145
7334
  },
7146
7335
  {
7147
- key: "question",
7148
- label: t("sev_question"),
7149
- count: noteCounts.question,
7150
- active: filter.severity === "question",
7151
- icon: "AlertTriangle",
7336
+ key: "enhance",
7337
+ label: t("sev_enhance"),
7338
+ count: noteCounts.enhance,
7339
+ active: filter.severity === "enhance",
7340
+ icon: "Plus",
7152
7341
  tone: "warn",
7153
- onClick: sev("question")
7342
+ onClick: sev("enhance")
7154
7343
  },
7155
7344
  {
7156
- key: "polish",
7157
- label: t("sev_polish"),
7158
- count: noteCounts.polish,
7159
- active: filter.severity === "polish",
7345
+ key: "design",
7346
+ label: t("sev_design"),
7347
+ count: noteCounts.design,
7348
+ active: filter.severity === "design",
7160
7349
  icon: "Pencil",
7161
7350
  tone: "accent",
7162
- onClick: sev("polish")
7351
+ onClick: sev("design")
7163
7352
  },
7164
7353
  {
7165
7354
  key: "open",
@@ -7228,7 +7417,48 @@ function NoteFilterBar() {
7228
7417
  ]
7229
7418
  }
7230
7419
  ),
7231
- /* @__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
+ ) })
7232
7462
  ] });
7233
7463
  }
7234
7464
  function Section({
@@ -7598,46 +7828,25 @@ function SettingsSheet({ onClose }) {
7598
7828
  );
7599
7829
  }
7600
7830
  function WelcomeCard() {
7601
- const { t, brand, dismissWelcome } = useQa();
7831
+ const { t, dismissWelcome } = useQa();
7602
7832
  return /* @__PURE__ */ jsxs(
7603
7833
  "div",
7604
7834
  {
7605
- className: "qa-rounded-xl qa-border qa-border-subtle qa-bg-1 qa-p-3 qa-space-y-2",
7835
+ 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",
7606
7836
  role: "note",
7607
7837
  children: [
7608
- /* @__PURE__ */ 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: [
7609
- /* @__PURE__ */ jsx(
7610
- "span",
7611
- {
7612
- "aria-hidden": "true",
7613
- className: "qa-shrink-0",
7614
- style: { width: 6, height: 6, background: "var(--qa-accent)" }
7615
- }
7616
- ),
7617
- t("welcome_title", { brand: brand.label })
7618
- ] }),
7619
- /* @__PURE__ */ jsxs("ul", { className: "qa-m-0 qa-space-y-1 qa-text-xs qa-text-mid", children: [
7620
- /* @__PURE__ */ jsxs("li", { className: "qa-flex qa-items-start qa-gap-1.5", children: [
7621
- /* @__PURE__ */ jsx(Icon, { name: "Crosshair", size: 12, className: "qa-shrink-0 qa-mt-1" }),
7622
- /* @__PURE__ */ jsx("span", { children: t("welcome_capture") })
7623
- ] }),
7624
- /* @__PURE__ */ jsxs("li", { className: "qa-flex qa-items-start qa-gap-1.5", children: [
7625
- /* @__PURE__ */ jsx(Icon, { name: "Pencil", size: 12, className: "qa-shrink-0 qa-mt-1" }),
7626
- /* @__PURE__ */ jsx("span", { children: t("welcome_say") })
7627
- ] }),
7628
- /* @__PURE__ */ jsxs("li", { className: "qa-flex qa-items-start qa-gap-1.5", children: [
7629
- /* @__PURE__ */ jsx(Icon, { name: "CheckCircle2", size: 12, className: "qa-shrink-0 qa-mt-1" }),
7630
- /* @__PURE__ */ jsx("span", { children: t("welcome_safe") })
7631
- ] })
7632
- ] }),
7838
+ /* @__PURE__ */ jsx(Icon, { name: "Crosshair", size: 13, className: "qa-shrink-0 qa-text-accent" }),
7839
+ /* @__PURE__ */ jsx("span", { className: "qa-flex-1 qa-text-xs qa-text-mid", children: t("welcome_capture") }),
7633
7840
  /* @__PURE__ */ jsx(
7634
7841
  "button",
7635
7842
  {
7636
7843
  type: "button",
7637
7844
  onClick: dismissWelcome,
7638
- 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",
7639
- style: { border: "none", cursor: "pointer" },
7640
- children: t("welcome_got_it")
7845
+ "aria-label": t("welcome_got_it"),
7846
+ title: t("welcome_got_it"),
7847
+ className: "qa-tap-icon qa-shrink-0 qa-rounded-full qa-text-mid qa-focus-ring",
7848
+ style: { background: "transparent", border: "none", cursor: "pointer", padding: 2 },
7849
+ children: /* @__PURE__ */ jsx(Icon, { name: "X", size: 13 })
7641
7850
  }
7642
7851
  )
7643
7852
  ]
@@ -7785,249 +7994,9 @@ function CredentialsSection() {
7785
7994
  })
7786
7995
  ] });
7787
7996
  }
7788
- var keyOf = (id, path) => `${id}::${path}`;
7789
- var DEFAULT_LANE_COLOR = "#4D9CFF";
7790
- function Lane({
7791
- group,
7792
- checked,
7793
- toggle,
7794
- pick: pick2,
7795
- onWalkFrom
7796
- }) {
7797
- const { lang, t, guideFailed, evidenceByStep } = useQa();
7798
- const { id, color = DEFAULT_LANE_COLOR, steps: steps2 } = group;
7799
- const done = steps2.filter((s) => checked.has(keyOf(id, s.path))).length;
7800
- const pct = steps2.length > 0 ? Math.round(done / steps2.length * 100) : 0;
7801
- const uncoveredRedCount = steps2.filter(
7802
- (s) => s.risk === "red" && !checked.has(keyOf(id, s.path))
7803
- ).length;
7804
- return /* @__PURE__ */ jsxs("div", { className: "qa-rounded-xl qa-border qa-border-subtle qa-bg-2 qa-p-3 qa-elev-1", children: [
7805
- /* @__PURE__ */ jsxs("div", { className: "qa-mb-2 qa-flex qa-items-center qa-gap-2", children: [
7806
- /* @__PURE__ */ jsx(
7807
- "span",
7808
- {
7809
- className: "qa-h-2.5 qa-w-2.5 qa-rounded-full",
7810
- style: { background: color }
7811
- }
7812
- ),
7813
- /* @__PURE__ */ jsx("span", { className: "qa-text-sm qa-font-bold qa-text-hi", children: pick2(group.role) }),
7814
- /* @__PURE__ */ jsxs("span", { className: "qa-ms-auto qa-text-11 qa-font-medium qa-text-slate-400", children: [
7815
- done,
7816
- "/",
7817
- steps2.length
7818
- ] }),
7819
- uncoveredRedCount > 0 && /* @__PURE__ */ jsx(
7820
- "span",
7821
- {
7822
- className: "qa-bg-danger-tint qa-text-danger qa-rounded qa-px-1 qa-text-10 qa-font-medium",
7823
- 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)`,
7824
- children: lang === "ar" ? `\u0623\u062D\u0645\u0631: ${uncoveredRedCount}` : `red: ${uncoveredRedCount}`
7825
- }
7826
- )
7827
- ] }),
7828
- /* @__PURE__ */ jsx(
7829
- "div",
7830
- {
7831
- className: "qa-mb-3 qa-h-1.5 qa-overflow-hidden qa-rounded-full",
7832
- style: { background: `${color}22` },
7833
- children: /* @__PURE__ */ jsx(
7834
- "div",
7835
- {
7836
- className: "qa-h-full qa-rounded-full qa-transition-all",
7837
- style: { width: `${pct}%`, background: color }
7838
- }
7839
- )
7840
- }
7841
- ),
7842
- /* @__PURE__ */ jsxs("ol", { className: "qa-relative qa-ms-1.5", children: [
7843
- /* @__PURE__ */ jsx(
7844
- "span",
7845
- {
7846
- className: "qa-absolute qa-top-1 qa-bottom-0 qa-w-px",
7847
- style: { insetInlineStart: "7px", background: `${color}40`, bottom: "4px" }
7848
- }
7849
- ),
7850
- steps2.map((s, i) => {
7851
- const k = keyOf(id, s.path);
7852
- const on = checked.has(k);
7853
- const failed = guideFailed.has(k);
7854
- const evidence = evidenceByStep.get(k);
7855
- const evidenceCount = evidence ? evidence.length : 0;
7856
- const riskColor = s.risk ? RISK_COLORS[s.risk] : RISK_COLORS.none;
7857
- 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;
7858
- return /* @__PURE__ */ jsxs("li", { className: "qa-relative qa-mb-2 qa-last-mb-0", children: [
7859
- /* @__PURE__ */ jsx(
7860
- "button",
7861
- {
7862
- onClick: () => onWalkFrom(k),
7863
- title: t("walk_take_me", { path: s.path }),
7864
- "aria-label": t("walk_take_me", { path: s.path }),
7865
- className: "qa-tap-icon qa-absolute qa-rounded-lg qa-text-mid qa-hover-text-slate-600",
7866
- style: {
7867
- top: 0,
7868
- insetInlineEnd: 0,
7869
- background: "transparent",
7870
- border: "none",
7871
- cursor: "pointer",
7872
- zIndex: 2
7873
- },
7874
- children: /* @__PURE__ */ jsx(Icon, { name: "Play", size: 12 })
7875
- }
7876
- ),
7877
- /* @__PURE__ */ jsxs(
7878
- "button",
7879
- {
7880
- onClick: () => toggle(k),
7881
- 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" : ""}`,
7882
- style: { background: failed ? void 0 : "transparent", border: "none", cursor: "pointer" },
7883
- children: [
7884
- /* @__PURE__ */ jsxs(
7885
- "span",
7886
- {
7887
- 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",
7888
- style: {
7889
- borderColor: failed ? "var(--qa-danger)" : color,
7890
- background: on ? color : failed ? "var(--qa-danger-tint)" : "var(--qa-surface-1)",
7891
- zIndex: 1
7892
- },
7893
- children: [
7894
- on && /* @__PURE__ */ jsx(Icon, { name: "Check", size: 10, strokeWidth: 3, className: "qa-text-white" }),
7895
- !on && failed && /* @__PURE__ */ jsx(Icon, { name: "AlertTriangle", size: 9, strokeWidth: 2.5, className: "qa-text-danger" })
7896
- ]
7897
- }
7898
- ),
7899
- /* @__PURE__ */ jsxs("span", { className: "qa-min-w-0", children: [
7900
- /* @__PURE__ */ jsxs("span", { className: "qa-flex qa-items-center qa-gap-1", children: [
7901
- /* @__PURE__ */ jsx(
7902
- "code",
7903
- {
7904
- className: "qa-rounded qa-px-1 qa-text-11 qa-font-semibold qa-dir-ltr qa-text-hi",
7905
- style: {
7906
- background: failed ? "var(--qa-danger-tint)" : `${color}14`,
7907
- textDecoration: on ? "line-through" : "none",
7908
- opacity: on ? 0.55 : 1
7909
- },
7910
- children: s.path
7911
- }
7912
- ),
7913
- /* @__PURE__ */ jsx(
7914
- "span",
7915
- {
7916
- className: "qa-inline-block qa-rounded-full qa-shrink-0",
7917
- style: {
7918
- width: "6px",
7919
- height: "6px",
7920
- background: riskColor,
7921
- flexShrink: 0
7922
- },
7923
- title: dotTitle
7924
- }
7925
- )
7926
- ] }),
7927
- /* @__PURE__ */ jsx(
7928
- "span",
7929
- {
7930
- className: "qa-mt-0.5 qa-block qa-text-11 qa-leading-relaxed qa-text-slate-500",
7931
- style: { opacity: on ? 0.5 : 1 },
7932
- children: pick2(s.what)
7933
- }
7934
- ),
7935
- 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 }) }),
7936
- 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") })
7937
- ] })
7938
- ]
7939
- }
7940
- )
7941
- ] }, `${k}-${i}`);
7942
- })
7943
- ] })
7944
- ] });
7945
- }
7946
- function GuideSection() {
7947
- const {
7948
- guideChecked,
7949
- toggleGuide,
7950
- t,
7951
- journey,
7952
- journeyIsGeneric,
7953
- pick: pick2,
7954
- lang,
7955
- startTestAlong,
7956
- startWalk,
7957
- testAlongSteps
7958
- } = useQa();
7959
- const all = journey.flatMap((g) => g.steps.map((s) => keyOf(g.id, s.path)));
7960
- const done = all.filter((k) => guideChecked.has(k)).length;
7961
- const pct = all.length > 0 ? Math.round(done / all.length * 100) : 0;
7962
- const coverage = computeCoverage(journey, guideChecked);
7963
- return /* @__PURE__ */ jsxs("div", { className: "qa-space-y-3", children: [
7964
- journeyIsGeneric && /* @__PURE__ */ jsx(
7965
- "p",
7966
- {
7967
- 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",
7968
- "data-qa-generic-plan": "true",
7969
- children: t("journey_generic")
7970
- }
7971
- ),
7972
- /* @__PURE__ */ jsxs("div", { className: "qa-rounded-xl qa-border qa-border-accent qa-bg-accent-tint qa-p-3 qa-elev-1", children: [
7973
- coverage.red.total > 0 && /* @__PURE__ */ jsxs("div", { className: "qa-mb-1 qa-flex qa-items-center qa-gap-1.5 qa-text-11", children: [
7974
- /* @__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" }),
7975
- /* @__PURE__ */ jsxs("span", { className: "qa-dir-ltr qa-font-semibold qa-text-hi", children: [
7976
- coverage.red.covered,
7977
- "/",
7978
- coverage.red.total,
7979
- " ",
7980
- lang === "ar" ? "\u0645\u063A\u0637\u0649" : "covered"
7981
- ] })
7982
- ] }),
7983
- /* @__PURE__ */ jsxs("div", { className: "qa-flex qa-items-center qa-justify-between qa-text-sm qa-font-semibold qa-text-hi", children: [
7984
- /* @__PURE__ */ jsx("span", { children: t("journey_title") }),
7985
- /* @__PURE__ */ jsxs("span", { className: "qa-dir-ltr", children: [
7986
- done,
7987
- "/",
7988
- all.length,
7989
- " \xB7 ",
7990
- pct,
7991
- "%"
7992
- ] })
7993
- ] }),
7994
- /* @__PURE__ */ jsx("div", { className: "qa-mt-2 qa-h-2 qa-overflow-hidden qa-rounded-full qa-bg-3", children: /* @__PURE__ */ jsx(
7995
- "div",
7996
- {
7997
- className: "qa-h-full qa-rounded-full qa-bg-accent qa-transition-all",
7998
- style: { width: `${pct}%` }
7999
- }
8000
- ) }),
8001
- /* @__PURE__ */ jsx("div", { className: "qa-mt-2 qa-flex qa-justify-end", children: /* @__PURE__ */ jsxs(
8002
- "button",
8003
- {
8004
- type: "button",
8005
- onClick: startTestAlong,
8006
- disabled: all.length === 0,
8007
- 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",
8008
- children: [
8009
- /* @__PURE__ */ jsx(Icon, { name: "Play", size: 13 }),
8010
- t("start_walkthrough")
8011
- ]
8012
- }
8013
- ) })
8014
- ] }),
8015
- journey.map((g) => /* @__PURE__ */ jsx(
8016
- Lane,
8017
- {
8018
- group: g,
8019
- checked: guideChecked,
8020
- toggle: toggleGuide,
8021
- onWalkFrom: (key) => {
8022
- const at = testAlongSteps.findIndex((step) => step.key === key);
8023
- startWalk("plan", at === -1 ? 0 : at);
8024
- },
8025
- pick: pick2
8026
- },
8027
- g.id
8028
- ))
8029
- ] });
8030
- }
7997
+
7998
+ // src/lib/features.ts
7999
+ var GUIDE_TAB_ENABLED = false;
8031
8000
  function isCoarsePointer() {
8032
8001
  if (typeof window === "undefined") return false;
8033
8002
  try {
@@ -8051,11 +8020,15 @@ function useCoarsePointer() {
8051
8020
  }, []);
8052
8021
  return coarse;
8053
8022
  }
8054
- var TABS = [
8023
+ var ALL_TABS = [
8055
8024
  { key: "notes", labelKey: "tab_notes", icon: "StickyNote" },
8056
8025
  { key: "logins", labelKey: "tab_logins", icon: "KeyRound" },
8057
8026
  { key: "guide", labelKey: "tab_guide", icon: "Map" }
8058
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
+ }
8059
8032
  function todayName() {
8060
8033
  return `qa-notes-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
8061
8034
  }
@@ -8090,7 +8063,7 @@ var PANEL_TRANSITION_WITH_LIFT = "opacity 200ms cubic-bezier(0.4,0,0.2,1), trans
8090
8063
  function QaPanel() {
8091
8064
  const {
8092
8065
  isOpen,
8093
- activeTab,
8066
+ activeTab: storedTab,
8094
8067
  setActiveTab,
8095
8068
  notes,
8096
8069
  exportZip,
@@ -8121,6 +8094,7 @@ function QaPanel() {
8121
8094
  panelCollapsed,
8122
8095
  setPanelCollapsed
8123
8096
  } = useQa();
8097
+ const activeTab = visibleTab(storedTab);
8124
8098
  const [confirmClear, setConfirmClear] = useState(false);
8125
8099
  const [naming, setNaming] = useState(false);
8126
8100
  const [filename, setFilename] = useState("");
@@ -8488,7 +8462,7 @@ function QaPanel() {
8488
8462
  ) })
8489
8463
  ] }),
8490
8464
  !simpleMode && activeTab === "logins" && /* @__PURE__ */ jsx(CredentialsSection, {}),
8491
- !simpleMode && activeTab === "guide" && /* @__PURE__ */ jsx(GuideSection, {})
8465
+ !simpleMode && GUIDE_TAB_ENABLED
8492
8466
  ] })
8493
8467
  ] }),
8494
8468
  settingsOpen && /* @__PURE__ */ jsx(SettingsSheet, { onClose: () => setSettingsOpen(false) }),
@@ -9104,16 +9078,16 @@ function unlockPageScroll() {
9104
9078
  var DRAG_THRESHOLD2 = 6;
9105
9079
  var TOUCH_DRAG_THRESHOLD = 12;
9106
9080
  var MIN_REGION_SIZE = 8;
9107
- var SEVERITIES2 = ["bug", "question", "polish"];
9081
+ var SEVERITIES2 = ["bug", "design", "enhance"];
9108
9082
  var SEVERITY_ICON = {
9109
9083
  bug: "Bug",
9110
- question: "AlertTriangle",
9111
- polish: "Pencil"
9084
+ design: "Pencil",
9085
+ enhance: "Plus"
9112
9086
  };
9113
9087
  var SEVERITY_LABEL_KEY2 = {
9114
9088
  bug: "sev_bug",
9115
- question: "sev_question",
9116
- polish: "sev_polish"
9089
+ design: "sev_design",
9090
+ enhance: "sev_enhance"
9117
9091
  };
9118
9092
  function clampRegionRect(rect) {
9119
9093
  const vw = typeof window !== "undefined" ? window.innerWidth : rect.left + rect.width;
@@ -10067,7 +10041,7 @@ function CaptureMode() {
10067
10041
  /* @__PURE__ */ jsx("span", { className: "qa-text-11 qa-text-mid qa-me-1", children: t("severity_label") }),
10068
10042
  SEVERITIES2.map((sev) => {
10069
10043
  const active = severity === sev;
10070
- 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";
10044
+ 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";
10071
10045
  return /* @__PURE__ */ jsxs(
10072
10046
  "button",
10073
10047
  {
@@ -10345,5 +10319,5 @@ function Qapture({ config }) {
10345
10319
  }
10346
10320
 
10347
10321
  export { Qapture, deleteQaDatabase, initQaStudio };
10348
- //# sourceMappingURL=chunk-NM3IDVEL.js.map
10349
- //# sourceMappingURL=chunk-NM3IDVEL.js.map
10322
+ //# sourceMappingURL=chunk-2JX7YU2P.js.map
10323
+ //# sourceMappingURL=chunk-2JX7YU2P.js.map