qapture2 0.7.7 → 0.7.9

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.
@@ -1741,6 +1741,8 @@ var STR = {
1741
1741
  loc_show: "Show captured location",
1742
1742
  loc_hide: "Hide location",
1743
1743
  loc_locate: "Locate on page",
1744
+ loc_other_page: "This one was filed on {path}, not the page you are on.",
1745
+ loc_go_there: "Go to {path}",
1744
1746
  journey_title: "Testing journey",
1745
1747
  journey_generic: "No test plan was set up for this project, so this is a general one that fits any app. Work through it, or ask whoever set up Qapture to add the real journey to qa.config.",
1746
1748
  export_name_title: "Name your export",
@@ -1869,6 +1871,13 @@ var STR = {
1869
1871
  retest_not_found: "Can't find that spot on this page \u2014 open the right page first",
1870
1872
  before_label: "Before",
1871
1873
  after_label: "After",
1874
+ followup_label: "What happened this time",
1875
+ followup_add: "Say what happened",
1876
+ followup_placeholder: "What did it do this time, and how is that different from what you asked for?",
1877
+ followup_hint: "The first note stays as it is. This is added under it as round two.",
1878
+ keep_only_retest: "Keep only these {n}",
1879
+ keep_only_retest_q: "Delete the other {n}?",
1880
+ keep_only_retest_hint: "Delete every note that is not waiting to be re-tested",
1872
1881
  welcome_title: "Testing {brand}",
1873
1882
  welcome_capture: 'Click "Capture from page", then click whatever looks wrong.',
1874
1883
  welcome_say: "Say what you expected in your own words. A screenshot is taken for you.",
@@ -1889,6 +1898,7 @@ var STR = {
1889
1898
  capture_screen: "Whole screen",
1890
1899
  walk_title: "Walkthrough",
1891
1900
  walk_take_me: "Take me to {path}",
1901
+ walk_taking_you: "Taking you there\u2026",
1892
1902
  walk_reload_hint: "Reload the page instead (use this if the app didn't move)",
1893
1903
  walk_note_title: "Point {n}",
1894
1904
  walk_still_broken: "Still broken",
@@ -1939,6 +1949,8 @@ var STR = {
1939
1949
  loc_show: "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0645\u0648\u0642\u0639 \u0627\u0644\u0645\u064F\u0644\u062A\u0642\u064E\u0637",
1940
1950
  loc_hide: "\u0625\u062E\u0641\u0627\u0621 \u0627\u0644\u0645\u0648\u0642\u0639",
1941
1951
  loc_locate: "\u0625\u0638\u0647\u0627\u0631 \u0639\u0644\u0649 \u0627\u0644\u0635\u0641\u062D\u0629",
1952
+ loc_other_page: "\u0633\u064F\u062C\u0651\u0644\u062A \u0647\u0630\u0647 \u0641\u064A {path}\u060C \u0648\u0644\u064A\u0633\u062A \u0627\u0644\u0635\u0641\u062D\u0629 \u0627\u0644\u062A\u064A \u0623\u0646\u062A \u0641\u064A\u0647\u0627.",
1953
+ loc_go_there: "\u0627\u0630\u0647\u0628 \u0625\u0644\u0649 {path}",
1942
1954
  journey_title: "\u0631\u062D\u0644\u0629 \u0627\u0644\u0627\u062E\u062A\u0628\u0627\u0631",
1943
1955
  journey_generic: "\u0644\u0645 \u064A\u062A\u0645 \u0625\u0639\u062F\u0627\u062F \u062E\u0637\u0629 \u0627\u062E\u062A\u0628\u0627\u0631 \u0644\u0647\u0630\u0627 \u0627\u0644\u0645\u0634\u0631\u0648\u0639\u060C \u0644\u0630\u0644\u0643 \u0647\u0630\u0647 \u062E\u0637\u0629 \u0639\u0627\u0645\u0629 \u062A\u0635\u0644\u062D \u0644\u0623\u064A \u062A\u0637\u0628\u064A\u0642. \u0627\u0639\u0645\u0644 \u0628\u0647\u0627\u060C \u0623\u0648 \u0627\u0637\u0644\u0628 \u0645\u0645\u0646 \u062B\u0628\u0651\u062A Qapture \u0623\u0646 \u064A\u0636\u064A\u0641 \u0631\u062D\u0644\u0629 \u0627\u0644\u0645\u0634\u0631\u0648\u0639 \u0641\u064A qa.config.",
1944
1956
  export_name_title: "\u0633\u0645\u0650\u0651 \u0645\u0644\u0641 \u0627\u0644\u062A\u0635\u062F\u064A\u0631",
@@ -2067,6 +2079,13 @@ var STR = {
2067
2079
  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
2080
  before_label: "\u0642\u0628\u0644",
2069
2081
  after_label: "\u0628\u0639\u062F",
2082
+ followup_label: "\u0645\u0627\u0630\u0627 \u062D\u062F\u062B \u0647\u0630\u0647 \u0627\u0644\u0645\u0631\u0629",
2083
+ followup_add: "\u0627\u0643\u062A\u0628 \u0645\u0627\u0630\u0627 \u062D\u062F\u062B",
2084
+ 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",
2085
+ 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.",
2086
+ keep_only_retest: "\u0623\u0628\u0642\u0650 \u0647\u0630\u0647 \u0627\u0644\u0640 {n} \u0641\u0642\u0637",
2087
+ keep_only_retest_q: "\u062D\u0630\u0641 \u0627\u0644\u0640 {n} \u0627\u0644\u0623\u062E\u0631\u0649\u061F",
2088
+ 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
2089
  welcome_title: "\u0627\u062E\u062A\u0628\u0627\u0631 {brand}",
2071
2090
  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
2091
  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.",
@@ -2087,6 +2106,7 @@ var STR = {
2087
2106
  capture_screen: "\u0627\u0644\u0634\u0627\u0634\u0629 \u0643\u0627\u0645\u0644\u0629",
2088
2107
  walk_title: "\u0627\u0644\u062C\u0648\u0644\u0629",
2089
2108
  walk_take_me: "\u062E\u0630\u0646\u064A \u0625\u0644\u0649 {path}",
2109
+ walk_taking_you: "\u062C\u0627\u0631\u064D \u0646\u0642\u0644\u0643\u2026",
2090
2110
  walk_reload_hint: "\u0623\u0639\u062F \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0635\u0641\u062D\u0629 \u0628\u062F\u0644\u064B\u0627 \u0645\u0646 \u0630\u0644\u0643 (\u0627\u0633\u062A\u062E\u062F\u0645\u0647 \u0625\u0630\u0627 \u0644\u0645 \u064A\u0646\u062A\u0642\u0644 \u0627\u0644\u062A\u0637\u0628\u064A\u0642)",
2091
2111
  walk_note_title: "\u0627\u0644\u0646\u0642\u0637\u0629 {n}",
2092
2112
  walk_still_broken: "\u0645\u0627 \u0632\u0627\u0644 \u0645\u0639\u0637\u0644\u064B\u0627",
@@ -2111,18 +2131,6 @@ function pick(value, lang) {
2111
2131
  if (typeof value === "string") return value;
2112
2132
  return value[lang] ?? value.en ?? "";
2113
2133
  }
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
2134
  function computeCoverage(journey, guideChecked, skipped) {
2127
2135
  const red = { total: 0, covered: 0 };
2128
2136
  const amber = { total: 0, covered: 0 };
@@ -2976,16 +2984,19 @@ function clipToPaintedArea(el, rect) {
2976
2984
  let top = rect.top;
2977
2985
  let right = rect.left + rect.width;
2978
2986
  let bottom = rect.top + rect.height;
2987
+ const doc = el.ownerDocument;
2988
+ const root2 = doc ? doc.documentElement : null;
2989
+ const body = doc ? doc.body : null;
2990
+ const rootStyle = root2 ? safeComputedStyle(root2) : null;
2991
+ const bodyStyle = body ? safeComputedStyle(body) : null;
2979
2992
  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";
2993
+ while (node && node !== root2) {
2994
+ const style = safeComputedStyle(node);
2995
+ if (!style) break;
2996
+ const propagatedX = node === body && rootStyle?.overflowX === "visible";
2997
+ const propagatedY = node === body && rootStyle?.overflowY === "visible";
2998
+ const clipsX = !propagatedX && style.overflowX !== "visible";
2999
+ const clipsY = !propagatedY && style.overflowY !== "visible";
2989
3000
  if (clipsX || clipsY) {
2990
3001
  const b = node.getBoundingClientRect();
2991
3002
  const padLeft = b.left + node.clientLeft;
@@ -3001,9 +3012,33 @@ function clipToPaintedArea(el, rect) {
3001
3012
  }
3002
3013
  node = node.parentElement;
3003
3014
  }
3015
+ const vpX = usedViewportOverflow(rootStyle?.overflowX, bodyStyle?.overflowX);
3016
+ const vpY = usedViewportOverflow(rootStyle?.overflowY, bodyStyle?.overflowY);
3017
+ if (vpX === "hidden" || vpX === "clip" || vpY === "hidden" || vpY === "clip") {
3018
+ const { vw, vh } = viewportSize();
3019
+ if (vpX === "hidden" || vpX === "clip") {
3020
+ left = Math.max(left, 0);
3021
+ right = Math.min(right, vw);
3022
+ }
3023
+ if (vpY === "hidden" || vpY === "clip") {
3024
+ top = Math.max(top, 0);
3025
+ bottom = Math.min(bottom, vh);
3026
+ }
3027
+ }
3004
3028
  if (right - left < 2 || bottom - top < 2) return rect;
3005
3029
  return { left, top, width: right - left, height: bottom - top };
3006
3030
  }
3031
+ function safeComputedStyle(node) {
3032
+ try {
3033
+ return getComputedStyle(node);
3034
+ } catch {
3035
+ return null;
3036
+ }
3037
+ }
3038
+ function usedViewportOverflow(rootValue, bodyValue) {
3039
+ if (rootValue && rootValue !== "visible") return rootValue;
3040
+ return bodyValue || "visible";
3041
+ }
3007
3042
  function intersectViewport(rect) {
3008
3043
  const { vw, vh } = viewportSize();
3009
3044
  const left = Math.max(0, rect.left);
@@ -3198,6 +3233,16 @@ function noteToMarkdown(note, opts) {
3198
3233
  }
3199
3234
  lines.push("");
3200
3235
  lines.push(oneLine(note.description) ? note.description.trim() : "_(no description)_");
3236
+ if (note.followUp && oneLine(note.followUp)) {
3237
+ lines.push("");
3238
+ lines.push(
3239
+ "> **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."
3240
+ );
3241
+ lines.push("");
3242
+ lines.push(`**What happened this time**${note.followUpAt ? ` (${oneLine(note.followUpAt)})` : ""}`);
3243
+ lines.push("");
3244
+ lines.push(note.followUp.trim());
3245
+ }
3201
3246
  const recordedSteps = note.context?.steps ?? [];
3202
3247
  if (recordedSteps.length) {
3203
3248
  const t0 = Date.parse(note.timestamp) || recordedSteps[recordedSteps.length - 1].t;
@@ -3284,7 +3329,7 @@ function fmtPct(n, d) {
3284
3329
  }
3285
3330
  function summariseSession(notes, stamp) {
3286
3331
  if (!notes.length) return "No points captured.";
3287
- let bugs = 0, enhancements = 0, designs = 0, verified = 0, awaiting = 0;
3332
+ let bugs = 0, enhancements = 0, designs = 0, verified = 0, awaiting = 0, secondRound = 0;
3288
3333
  const pages = /* @__PURE__ */ new Set();
3289
3334
  let earliest = Number.POSITIVE_INFINITY;
3290
3335
  let latest = 0;
@@ -3296,6 +3341,7 @@ function summariseSession(notes, stamp) {
3296
3341
  const status = n.status ?? "open";
3297
3342
  if (status === "verified") verified++;
3298
3343
  else if (status === "fixed") awaiting++;
3344
+ if (n.followUp && n.followUp.trim()) secondRound++;
3299
3345
  pages.add((n.route || "/").split("?")[0]);
3300
3346
  const t = Date.parse(n.timestamp);
3301
3347
  if (Number.isFinite(t)) {
@@ -3311,6 +3357,7 @@ function summariseSession(notes, stamp) {
3311
3357
  if (designs) parts.push(`${designs} design`);
3312
3358
  if (verified) parts.push(`${verified} verified`);
3313
3359
  if (awaiting) parts.push(`${awaiting} awaiting re-test`);
3360
+ if (secondRound) parts.push(`${secondRound} back for a second round`);
3314
3361
  parts.push(`${pages.size} page${pages.size === 1 ? "" : "s"}`);
3315
3362
  if (Number.isFinite(earliest) && latest > earliest) {
3316
3363
  const minutes = Math.max(1, Math.round((latest - earliest) / 6e4));
@@ -3365,6 +3412,14 @@ The tester wants something added or changed: a new section, a new feature, a dif
3365
3412
 
3366
3413
  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.
3367
3414
 
3415
+ ### A point with a "round 2" on it
3416
+
3417
+ 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.
3418
+
3419
+ 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.
3420
+
3421
+ 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.
3422
+
3368
3423
  ### When the tag and the words disagree
3369
3424
 
3370
3425
  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.
@@ -4169,6 +4224,13 @@ var DENSE_NOTES_KEY = "denseNotes";
4169
4224
  var PANEL_OPEN_KEY = "panelOpen";
4170
4225
  var ACTIVE_TAB_KEY = "activeTab";
4171
4226
  var WALK_KEY = "walk";
4227
+ var SOFT_NAV_PROOF_MS = 700;
4228
+ function pageSignature() {
4229
+ if (typeof document === "undefined" || !document.body) return "";
4230
+ const body = document.body;
4231
+ const text = body.textContent || "";
4232
+ return [document.title, body.childElementCount, text.length, text.slice(0, 200)].join("\0");
4233
+ }
4172
4234
  var GUIDE_SKIPPED_KEY = "guideSkipped";
4173
4235
  var NOTICE_QUEUE_CAP = 3;
4174
4236
  var NOTICE_DURATION_INFO = 4e3;
@@ -4609,6 +4671,11 @@ function QaProvider({
4609
4671
  }
4610
4672
  if (patch.severity !== void 0) updated.severity = patch.severity;
4611
4673
  if (patch.status !== void 0) updated.status = patch.status;
4674
+ if (patch.followUp !== void 0) {
4675
+ const text = patch.followUp.trim();
4676
+ updated.followUp = text || void 0;
4677
+ updated.followUpAt = text ? nowIso() : void 0;
4678
+ }
4612
4679
  applyNotes((prev) => prev.map((n) => n.id === id ? updated : n));
4613
4680
  const persisted = await idb.put(updated);
4614
4681
  if (!persisted) {
@@ -5200,16 +5267,23 @@ function QaProvider({
5200
5267
  const walkNavigate = React.useCallback((path, hard = false) => {
5201
5268
  if (typeof window === "undefined" || !path) return;
5202
5269
  const target = path.startsWith("/") ? path : `/${path}`;
5203
- if (hard) {
5270
+ if (hard || target === window.location.pathname) {
5204
5271
  window.location.assign(target);
5205
5272
  return;
5206
5273
  }
5274
+ const before = pageSignature();
5207
5275
  try {
5208
- window.history.pushState({}, "", target);
5209
- window.dispatchEvent(new PopStateEvent("popstate"));
5276
+ window.history.pushState(window.history.state, "", target);
5277
+ window.dispatchEvent(new PopStateEvent("popstate", { state: window.history.state }));
5210
5278
  } catch {
5211
5279
  window.location.assign(target);
5280
+ return;
5212
5281
  }
5282
+ window.setTimeout(() => {
5283
+ if (window.location.pathname !== target) return;
5284
+ if (pageSignature() !== before) return;
5285
+ window.location.assign(target);
5286
+ }, SOFT_NAV_PROOF_MS);
5213
5287
  }, []);
5214
5288
  React.useEffect(() => {
5215
5289
  if (typeof window === "undefined") return;
@@ -6092,11 +6166,17 @@ function flashLocate(target) {
6092
6166
  paint(rect);
6093
6167
  }
6094
6168
  }
6095
- function LocationReveal({ target }) {
6096
- const { t } = useQa();
6169
+ function LocationReveal({
6170
+ target,
6171
+ route
6172
+ }) {
6173
+ const { t, walkNavigate } = useQa();
6097
6174
  const [open, setOpen] = React.useState(false);
6098
6175
  if (!target) return null;
6099
6176
  const r = target.rect;
6177
+ const notePath = (route || "").split("?")[0].split("#")[0];
6178
+ const here = typeof window === "undefined" ? "" : window.location.pathname;
6179
+ const elsewhere = !!notePath && notePath !== here;
6100
6180
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-rounded-lg qa-border qa-border-subtle qa-bg-2", children: [
6101
6181
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-flex qa-items-center qa-gap-1.5 qa-px-2 qa-py-1.5 qa-text-11", children: [
6102
6182
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "CheckCircle2", size: 14, className: "qa-text-success" }),
@@ -6160,7 +6240,21 @@ function LocationReveal({ target }) {
6160
6240
  "\xD7",
6161
6241
  Math.round(r.height)
6162
6242
  ] }),
6163
- /* @__PURE__ */ jsxRuntime.jsxs(
6243
+ elsewhere ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6244
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-mt-1 qa-mb-1 qa-m-0 qa-text-lo", children: t("loc_other_page", { path: notePath }) }),
6245
+ /* @__PURE__ */ jsxRuntime.jsxs(
6246
+ "button",
6247
+ {
6248
+ onClick: () => walkNavigate(notePath),
6249
+ className: "qa-inline-flex qa-items-center qa-gap-1 qa-rounded-md qa-px-2 qa-py-1 qa-font-medium qa-tap qa-bg-accent",
6250
+ style: { border: "none", cursor: "pointer" },
6251
+ children: [
6252
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "MapPinned", size: 12 }),
6253
+ t("loc_go_there", { path: notePath })
6254
+ ]
6255
+ }
6256
+ )
6257
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
6164
6258
  "button",
6165
6259
  {
6166
6260
  onClick: () => flashLocate(target),
@@ -6604,6 +6698,8 @@ function NoteItem({
6604
6698
  const thumbUrl = useObjectUrl(editing ? img ?? void 0 : note.screenshot);
6605
6699
  const afterUrl = useObjectUrl(note.afterScreenshot);
6606
6700
  const [retesting, setRetesting] = React.useState(false);
6701
+ const [editingFollowUp, setEditingFollowUp] = React.useState(false);
6702
+ const [followUpDraft, setFollowUpDraft] = React.useState(note.followUp ?? "");
6607
6703
  const [open, setOpen] = React.useState(false);
6608
6704
  const severity = note.severity ?? "bug";
6609
6705
  const status = note.status ?? "open";
@@ -6876,7 +6972,7 @@ function NoteItem({
6876
6972
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "MapPin", size: 12, className: "qa-shrink-0" }),
6877
6973
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-truncate qa-dir-ltr", title: note.url, children: note.route })
6878
6974
  ] }),
6879
- note.target && /* @__PURE__ */ jsxRuntime.jsx(LocationReveal, { target: note.target }),
6975
+ note.target && /* @__PURE__ */ jsxRuntime.jsx(LocationReveal, { target: note.target, route: note.route }),
6880
6976
  contextEventCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { children: t("context_attached", { n: contextEventCount }) }),
6881
6977
  stepCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-flex qa-items-center qa-gap-1", children: [
6882
6978
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "ClipboardList", size: 12, className: "qa-shrink-0" }),
@@ -6937,7 +7033,96 @@ function NoteItem({
6937
7033
  style: { borderColor: "var(--qa-success)" }
6938
7034
  }
6939
7035
  )
6940
- ] })
7036
+ ] }),
7037
+ (status === "fixed" || note.followUp) && /* @__PURE__ */ jsxRuntime.jsx(
7038
+ "div",
7039
+ {
7040
+ className: "qa-mt-2 qa-rounded-lg qa-border qa-border-subtle qa-bg-2 qa-p-2",
7041
+ "data-qa-followup": "true",
7042
+ children: editingFollowUp ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7043
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "qa-text-10 qa-text-lo", htmlFor: `qa-fu-${note.id}`, children: t("followup_label") }),
7044
+ /* @__PURE__ */ jsxRuntime.jsx(
7045
+ "textarea",
7046
+ {
7047
+ id: `qa-fu-${note.id}`,
7048
+ value: followUpDraft,
7049
+ onChange: (e) => setFollowUpDraft(e.target.value),
7050
+ placeholder: t("followup_placeholder"),
7051
+ rows: 3,
7052
+ 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",
7053
+ style: { outline: "none", resize: "vertical" }
7054
+ }
7055
+ ),
7056
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-mt-1 qa-text-10 qa-text-lo", children: t("followup_hint") }),
7057
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-mt-1.5 qa-flex qa-gap-2", children: [
7058
+ /* @__PURE__ */ jsxRuntime.jsxs(
7059
+ "button",
7060
+ {
7061
+ type: "button",
7062
+ onClick: () => {
7063
+ void updateNote(note.id, { followUp: followUpDraft });
7064
+ setEditingFollowUp(false);
7065
+ },
7066
+ 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",
7067
+ style: { border: "none", cursor: "pointer" },
7068
+ children: [
7069
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Check", size: 12 }),
7070
+ t("save")
7071
+ ]
7072
+ }
7073
+ ),
7074
+ /* @__PURE__ */ jsxRuntime.jsx(
7075
+ "button",
7076
+ {
7077
+ type: "button",
7078
+ onClick: () => {
7079
+ setFollowUpDraft(note.followUp ?? "");
7080
+ setEditingFollowUp(false);
7081
+ },
7082
+ className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-11 qa-text-mid",
7083
+ style: { background: "transparent", cursor: "pointer" },
7084
+ children: t("cancel")
7085
+ }
7086
+ )
7087
+ ] })
7088
+ ] }) : note.followUp ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
7089
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-flex qa-items-center qa-gap-1", children: [
7090
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "RotateCcw", size: 11, className: "qa-shrink-0 qa-text-warn" }),
7091
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-text-10 qa-text-warn", children: t("followup_label") }),
7092
+ /* @__PURE__ */ jsxRuntime.jsx(
7093
+ "button",
7094
+ {
7095
+ type: "button",
7096
+ onClick: () => {
7097
+ setFollowUpDraft(note.followUp ?? "");
7098
+ setEditingFollowUp(true);
7099
+ },
7100
+ "aria-label": t("edit"),
7101
+ className: "qa-tap-icon qa-ms-auto qa-text-lo",
7102
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7103
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Pencil", size: 11 })
7104
+ }
7105
+ )
7106
+ ] }),
7107
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-mt-1 qa-whitespace-pre-wrap qa-break-words qa-text-xs qa-text-hi", children: note.followUp })
7108
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
7109
+ "button",
7110
+ {
7111
+ type: "button",
7112
+ onClick: () => {
7113
+ setFollowUpDraft("");
7114
+ setEditingFollowUp(true);
7115
+ },
7116
+ className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-text-11 qa-font-medium qa-text-accent",
7117
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7118
+ children: [
7119
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Plus", size: 12 }),
7120
+ t("followup_add")
7121
+ ]
7122
+ }
7123
+ )
7124
+ }
7125
+ )
6941
7126
  ] })
6942
7127
  ]
6943
7128
  }
@@ -7170,8 +7355,11 @@ function FilterChip({ chip }) {
7170
7355
  );
7171
7356
  }
7172
7357
  function NoteFilterBar() {
7173
- const { filter, setFilter, noteCounts, notes, t } = useQa();
7358
+ const { filter, setFilter, noteCounts, notes, deleteNotes, t } = useQa();
7359
+ const [confirmPrune, setConfirmPrune] = React.useState(false);
7174
7360
  if (notes.length < 3) return null;
7361
+ const others = notes.filter((n) => (n.status ?? "open") !== "fixed");
7362
+ const showPrune = filter.status === "fixed" && noteCounts.fixed > 0 && others.length > 0;
7175
7363
  const sev = (key) => () => setFilter({ severity: filter.severity === key ? "all" : key });
7176
7364
  const stat = (key) => () => setFilter({ status: filter.status === key ? "all" : key });
7177
7365
  const chips = [
@@ -7276,7 +7464,48 @@ function NoteFilterBar() {
7276
7464
  ]
7277
7465
  }
7278
7466
  ),
7279
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-flex qa-flex-wrap qa-gap-1", children: chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(FilterChip, { chip }, chip.key)) })
7467
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-flex qa-flex-wrap qa-gap-1", children: chips.map((chip) => /* @__PURE__ */ jsxRuntime.jsx(FilterChip, { chip }, chip.key)) }),
7468
+ showPrune && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-text-11", "data-qa-prune": "true", children: confirmPrune ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-text-mid", children: [
7469
+ t("keep_only_retest_q", { n: others.length }),
7470
+ " ",
7471
+ /* @__PURE__ */ jsxRuntime.jsx(
7472
+ "button",
7473
+ {
7474
+ type: "button",
7475
+ onClick: () => {
7476
+ void deleteNotes(others.map((n) => n.id));
7477
+ setConfirmPrune(false);
7478
+ },
7479
+ className: "qa-font-semibold qa-text-danger qa-tap",
7480
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7481
+ children: t("yes")
7482
+ }
7483
+ ),
7484
+ " / ",
7485
+ /* @__PURE__ */ jsxRuntime.jsx(
7486
+ "button",
7487
+ {
7488
+ type: "button",
7489
+ onClick: () => setConfirmPrune(false),
7490
+ className: "qa-text-accent qa-tap",
7491
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7492
+ children: t("no")
7493
+ }
7494
+ )
7495
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
7496
+ "button",
7497
+ {
7498
+ type: "button",
7499
+ onClick: () => setConfirmPrune(true),
7500
+ title: t("keep_only_retest_hint"),
7501
+ className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-text-lo",
7502
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7503
+ children: [
7504
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Trash", size: 11 }),
7505
+ t("keep_only_retest", { n: noteCounts.fixed })
7506
+ ]
7507
+ }
7508
+ ) })
7280
7509
  ] });
7281
7510
  }
7282
7511
  function Section({
@@ -7812,249 +8041,9 @@ function CredentialsSection() {
7812
8041
  })
7813
8042
  ] });
7814
8043
  }
7815
- var keyOf = (id, path) => `${id}::${path}`;
7816
- var DEFAULT_LANE_COLOR = "#4D9CFF";
7817
- function Lane({
7818
- group,
7819
- checked,
7820
- toggle,
7821
- pick: pick2,
7822
- onWalkFrom
7823
- }) {
7824
- const { lang, t, guideFailed, evidenceByStep } = useQa();
7825
- const { id, color = DEFAULT_LANE_COLOR, steps: steps2 } = group;
7826
- const done = steps2.filter((s) => checked.has(keyOf(id, s.path))).length;
7827
- const pct = steps2.length > 0 ? Math.round(done / steps2.length * 100) : 0;
7828
- const uncoveredRedCount = steps2.filter(
7829
- (s) => s.risk === "red" && !checked.has(keyOf(id, s.path))
7830
- ).length;
7831
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-rounded-xl qa-border qa-border-subtle qa-bg-2 qa-p-3 qa-elev-1", children: [
7832
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-mb-2 qa-flex qa-items-center qa-gap-2", children: [
7833
- /* @__PURE__ */ jsxRuntime.jsx(
7834
- "span",
7835
- {
7836
- className: "qa-h-2.5 qa-w-2.5 qa-rounded-full",
7837
- style: { background: color }
7838
- }
7839
- ),
7840
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-text-sm qa-font-bold qa-text-hi", children: pick2(group.role) }),
7841
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-ms-auto qa-text-11 qa-font-medium qa-text-slate-400", children: [
7842
- done,
7843
- "/",
7844
- steps2.length
7845
- ] }),
7846
- uncoveredRedCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(
7847
- "span",
7848
- {
7849
- className: "qa-bg-danger-tint qa-text-danger qa-rounded qa-px-1 qa-text-10 qa-font-medium",
7850
- 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)`,
7851
- children: lang === "ar" ? `\u0623\u062D\u0645\u0631: ${uncoveredRedCount}` : `red: ${uncoveredRedCount}`
7852
- }
7853
- )
7854
- ] }),
7855
- /* @__PURE__ */ jsxRuntime.jsx(
7856
- "div",
7857
- {
7858
- className: "qa-mb-3 qa-h-1.5 qa-overflow-hidden qa-rounded-full",
7859
- style: { background: `${color}22` },
7860
- children: /* @__PURE__ */ jsxRuntime.jsx(
7861
- "div",
7862
- {
7863
- className: "qa-h-full qa-rounded-full qa-transition-all",
7864
- style: { width: `${pct}%`, background: color }
7865
- }
7866
- )
7867
- }
7868
- ),
7869
- /* @__PURE__ */ jsxRuntime.jsxs("ol", { className: "qa-relative qa-ms-1.5", children: [
7870
- /* @__PURE__ */ jsxRuntime.jsx(
7871
- "span",
7872
- {
7873
- className: "qa-absolute qa-top-1 qa-bottom-0 qa-w-px",
7874
- style: { insetInlineStart: "7px", background: `${color}40`, bottom: "4px" }
7875
- }
7876
- ),
7877
- steps2.map((s, i) => {
7878
- const k = keyOf(id, s.path);
7879
- const on = checked.has(k);
7880
- const failed = guideFailed.has(k);
7881
- const evidence = evidenceByStep.get(k);
7882
- const evidenceCount = evidence ? evidence.length : 0;
7883
- const riskColor = s.risk ? RISK_COLORS[s.risk] : RISK_COLORS.none;
7884
- 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;
7885
- return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "qa-relative qa-mb-2 qa-last-mb-0", children: [
7886
- /* @__PURE__ */ jsxRuntime.jsx(
7887
- "button",
7888
- {
7889
- onClick: () => onWalkFrom(k),
7890
- title: t("walk_take_me", { path: s.path }),
7891
- "aria-label": t("walk_take_me", { path: s.path }),
7892
- className: "qa-tap-icon qa-absolute qa-rounded-lg qa-text-mid qa-hover-text-slate-600",
7893
- style: {
7894
- top: 0,
7895
- insetInlineEnd: 0,
7896
- background: "transparent",
7897
- border: "none",
7898
- cursor: "pointer",
7899
- zIndex: 2
7900
- },
7901
- children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Play", size: 12 })
7902
- }
7903
- ),
7904
- /* @__PURE__ */ jsxRuntime.jsxs(
7905
- "button",
7906
- {
7907
- onClick: () => toggle(k),
7908
- 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" : ""}`,
7909
- style: { background: failed ? void 0 : "transparent", border: "none", cursor: "pointer" },
7910
- children: [
7911
- /* @__PURE__ */ jsxRuntime.jsxs(
7912
- "span",
7913
- {
7914
- 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",
7915
- style: {
7916
- borderColor: failed ? "var(--qa-danger)" : color,
7917
- background: on ? color : failed ? "var(--qa-danger-tint)" : "var(--qa-surface-1)",
7918
- zIndex: 1
7919
- },
7920
- children: [
7921
- on && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Check", size: 10, strokeWidth: 3, className: "qa-text-white" }),
7922
- !on && failed && /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "AlertTriangle", size: 9, strokeWidth: 2.5, className: "qa-text-danger" })
7923
- ]
7924
- }
7925
- ),
7926
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-min-w-0", children: [
7927
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-flex qa-items-center qa-gap-1", children: [
7928
- /* @__PURE__ */ jsxRuntime.jsx(
7929
- "code",
7930
- {
7931
- className: "qa-rounded qa-px-1 qa-text-11 qa-font-semibold qa-dir-ltr qa-text-hi",
7932
- style: {
7933
- background: failed ? "var(--qa-danger-tint)" : `${color}14`,
7934
- textDecoration: on ? "line-through" : "none",
7935
- opacity: on ? 0.55 : 1
7936
- },
7937
- children: s.path
7938
- }
7939
- ),
7940
- /* @__PURE__ */ jsxRuntime.jsx(
7941
- "span",
7942
- {
7943
- className: "qa-inline-block qa-rounded-full qa-shrink-0",
7944
- style: {
7945
- width: "6px",
7946
- height: "6px",
7947
- background: riskColor,
7948
- flexShrink: 0
7949
- },
7950
- title: dotTitle
7951
- }
7952
- )
7953
- ] }),
7954
- /* @__PURE__ */ jsxRuntime.jsx(
7955
- "span",
7956
- {
7957
- className: "qa-mt-0.5 qa-block qa-text-11 qa-leading-relaxed qa-text-slate-500",
7958
- style: { opacity: on ? 0.5 : 1 },
7959
- children: pick2(s.what)
7960
- }
7961
- ),
7962
- 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 }) }),
7963
- 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") })
7964
- ] })
7965
- ]
7966
- }
7967
- )
7968
- ] }, `${k}-${i}`);
7969
- })
7970
- ] })
7971
- ] });
7972
- }
7973
- function GuideSection() {
7974
- const {
7975
- guideChecked,
7976
- toggleGuide,
7977
- t,
7978
- journey,
7979
- journeyIsGeneric,
7980
- pick: pick2,
7981
- lang,
7982
- startTestAlong,
7983
- startWalk,
7984
- testAlongSteps
7985
- } = useQa();
7986
- const all = journey.flatMap((g) => g.steps.map((s) => keyOf(g.id, s.path)));
7987
- const done = all.filter((k) => guideChecked.has(k)).length;
7988
- const pct = all.length > 0 ? Math.round(done / all.length * 100) : 0;
7989
- const coverage = computeCoverage(journey, guideChecked);
7990
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-space-y-3", children: [
7991
- journeyIsGeneric && /* @__PURE__ */ jsxRuntime.jsx(
7992
- "p",
7993
- {
7994
- 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",
7995
- "data-qa-generic-plan": "true",
7996
- children: t("journey_generic")
7997
- }
7998
- ),
7999
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-rounded-xl qa-border qa-border-accent qa-bg-accent-tint qa-p-3 qa-elev-1", children: [
8000
- 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: [
8001
- /* @__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" }),
8002
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-dir-ltr qa-font-semibold qa-text-hi", children: [
8003
- coverage.red.covered,
8004
- "/",
8005
- coverage.red.total,
8006
- " ",
8007
- lang === "ar" ? "\u0645\u063A\u0637\u0649" : "covered"
8008
- ] })
8009
- ] }),
8010
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-flex qa-items-center qa-justify-between qa-text-sm qa-font-semibold qa-text-hi", children: [
8011
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("journey_title") }),
8012
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "qa-dir-ltr", children: [
8013
- done,
8014
- "/",
8015
- all.length,
8016
- " \xB7 ",
8017
- pct,
8018
- "%"
8019
- ] })
8020
- ] }),
8021
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-mt-2 qa-h-2 qa-overflow-hidden qa-rounded-full qa-bg-3", children: /* @__PURE__ */ jsxRuntime.jsx(
8022
- "div",
8023
- {
8024
- className: "qa-h-full qa-rounded-full qa-bg-accent qa-transition-all",
8025
- style: { width: `${pct}%` }
8026
- }
8027
- ) }),
8028
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "qa-mt-2 qa-flex qa-justify-end", children: /* @__PURE__ */ jsxRuntime.jsxs(
8029
- "button",
8030
- {
8031
- type: "button",
8032
- onClick: startTestAlong,
8033
- disabled: all.length === 0,
8034
- 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",
8035
- children: [
8036
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Play", size: 13 }),
8037
- t("start_walkthrough")
8038
- ]
8039
- }
8040
- ) })
8041
- ] }),
8042
- journey.map((g) => /* @__PURE__ */ jsxRuntime.jsx(
8043
- Lane,
8044
- {
8045
- group: g,
8046
- checked: guideChecked,
8047
- toggle: toggleGuide,
8048
- onWalkFrom: (key) => {
8049
- const at = testAlongSteps.findIndex((step) => step.key === key);
8050
- startWalk("plan", at === -1 ? 0 : at);
8051
- },
8052
- pick: pick2
8053
- },
8054
- g.id
8055
- ))
8056
- ] });
8057
- }
8044
+
8045
+ // src/lib/features.ts
8046
+ var GUIDE_TAB_ENABLED = false;
8058
8047
  function isCoarsePointer() {
8059
8048
  if (typeof window === "undefined") return false;
8060
8049
  try {
@@ -8078,11 +8067,15 @@ function useCoarsePointer() {
8078
8067
  }, []);
8079
8068
  return coarse;
8080
8069
  }
8081
- var TABS = [
8070
+ var ALL_TABS = [
8082
8071
  { key: "notes", labelKey: "tab_notes", icon: "StickyNote" },
8083
8072
  { key: "logins", labelKey: "tab_logins", icon: "KeyRound" },
8084
8073
  { key: "guide", labelKey: "tab_guide", icon: "Map" }
8085
8074
  ];
8075
+ var TABS = ALL_TABS.filter((tab) => tab.key !== "guide" || GUIDE_TAB_ENABLED);
8076
+ function visibleTab(tab) {
8077
+ return tab === "guide" && !GUIDE_TAB_ENABLED ? "notes" : tab;
8078
+ }
8086
8079
  function todayName() {
8087
8080
  return `qa-notes-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
8088
8081
  }
@@ -8117,7 +8110,7 @@ var PANEL_TRANSITION_WITH_LIFT = "opacity 200ms cubic-bezier(0.4,0,0.2,1), trans
8117
8110
  function QaPanel() {
8118
8111
  const {
8119
8112
  isOpen,
8120
- activeTab,
8113
+ activeTab: storedTab,
8121
8114
  setActiveTab,
8122
8115
  notes,
8123
8116
  exportZip,
@@ -8148,6 +8141,7 @@ function QaPanel() {
8148
8141
  panelCollapsed,
8149
8142
  setPanelCollapsed
8150
8143
  } = useQa();
8144
+ const activeTab = visibleTab(storedTab);
8151
8145
  const [confirmClear, setConfirmClear] = React.useState(false);
8152
8146
  const [naming, setNaming] = React.useState(false);
8153
8147
  const [filename, setFilename] = React.useState("");
@@ -8515,7 +8509,7 @@ function QaPanel() {
8515
8509
  ) })
8516
8510
  ] }),
8517
8511
  !simpleMode && activeTab === "logins" && /* @__PURE__ */ jsxRuntime.jsx(CredentialsSection, {}),
8518
- !simpleMode && activeTab === "guide" && /* @__PURE__ */ jsxRuntime.jsx(GuideSection, {})
8512
+ !simpleMode && GUIDE_TAB_ENABLED
8519
8513
  ] })
8520
8514
  ] }),
8521
8515
  settingsOpen && /* @__PURE__ */ jsxRuntime.jsx(SettingsSheet, { onClose: () => setSettingsOpen(false) }),
@@ -8722,6 +8716,20 @@ var RISK_COLOR = {
8722
8716
  amber: "var(--qa-warn)",
8723
8717
  green: "var(--qa-success)"
8724
8718
  };
8719
+ function useCurrentPath() {
8720
+ const [path, setPath] = React.useState(() => typeof window === "undefined" ? "" : window.location.pathname);
8721
+ React.useEffect(() => {
8722
+ if (typeof window === "undefined") return;
8723
+ const sync = () => setPath(window.location.pathname);
8724
+ window.addEventListener("popstate", sync);
8725
+ window.addEventListener("hashchange", sync);
8726
+ return () => {
8727
+ window.removeEventListener("popstate", sync);
8728
+ window.removeEventListener("hashchange", sync);
8729
+ };
8730
+ }, []);
8731
+ return path;
8732
+ }
8725
8733
  function WalkHud() {
8726
8734
  const {
8727
8735
  dir,
@@ -8741,10 +8749,13 @@ function WalkHud() {
8741
8749
  startCapture,
8742
8750
  updateNote,
8743
8751
  retestNote,
8744
- notes
8752
+ notes,
8753
+ notesLoading
8745
8754
  } = useQa();
8746
8755
  const index = Math.min(walk.index, Math.max(0, walkStops.length - 1));
8747
8756
  const stop = walkStops[index];
8757
+ const currentPath = useCurrentPath();
8758
+ const [going, setGoing] = React.useState(false);
8748
8759
  const backIcon = dir === "rtl" ? "ChevronRight" : "ChevronLeft";
8749
8760
  const nextIcon = dir === "rtl" ? "ChevronLeft" : "ChevronRight";
8750
8761
  React.useEffect(() => {
@@ -8758,8 +8769,9 @@ function WalkHud() {
8758
8769
  if (latest?.target) flashLocate(latest.target);
8759
8770
  }, [index, walk.source]);
8760
8771
  React.useEffect(() => {
8772
+ if (notesLoading) return;
8761
8773
  if (walk.active && walkStops.length === 0) exitWalk();
8762
- }, [walk.active, walkStops.length, exitWalk]);
8774
+ }, [walk.active, walkStops.length, exitWalk, notesLoading]);
8763
8775
  if (!walk.active || !stop) return null;
8764
8776
  const atLast = index >= walkStops.length - 1;
8765
8777
  let title;
@@ -8920,7 +8932,7 @@ function WalkHud() {
8920
8932
  )
8921
8933
  ] });
8922
8934
  }
8923
- const onThisPage = stop.path === ANY_PAGE || typeof window !== "undefined" && stop.path === window.location.pathname;
8935
+ const onThisPage = stop.path === ANY_PAGE || stop.path === currentPath;
8924
8936
  return /* @__PURE__ */ jsxRuntime.jsxs(
8925
8937
  "div",
8926
8938
  {
@@ -8956,12 +8968,24 @@ function WalkHud() {
8956
8968
  "button",
8957
8969
  {
8958
8970
  type: "button",
8959
- onClick: () => walkNavigate(stop.path),
8971
+ disabled: going,
8972
+ onClick: () => {
8973
+ setGoing(true);
8974
+ walkNavigate(stop.path);
8975
+ window.setTimeout(() => setGoing(false), 900);
8976
+ },
8960
8977
  className: "qa-tap qa-inline-flex qa-flex-1 qa-items-center qa-justify-center qa-gap-1.5 qa-rounded-lg qa-bg-accent qa-px-2 qa-py-1.5 qa-text-11 qa-font-semibold",
8961
- style: { border: "none", cursor: "pointer" },
8978
+ style: { border: "none", cursor: going ? "default" : "pointer" },
8962
8979
  children: [
8963
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "MapPinned", size: 13 }),
8964
- t("walk_take_me", { path: stop.path })
8980
+ /* @__PURE__ */ jsxRuntime.jsx(
8981
+ Icon,
8982
+ {
8983
+ name: going ? "Loader2" : "MapPinned",
8984
+ size: 13,
8985
+ className: going ? "qa-animate-spin" : void 0
8986
+ }
8987
+ ),
8988
+ going ? t("walk_taking_you") : t("walk_take_me", { path: stop.path })
8965
8989
  ]
8966
8990
  }
8967
8991
  ),
@@ -10374,5 +10398,5 @@ function Qapture({ config }) {
10374
10398
  exports.Qapture = Qapture;
10375
10399
  exports.deleteQaDatabase = deleteQaDatabase;
10376
10400
  exports.initQaStudio = initQaStudio;
10377
- //# sourceMappingURL=chunk-WU7QMHPK.cjs.map
10378
- //# sourceMappingURL=chunk-WU7QMHPK.cjs.map
10401
+ //# sourceMappingURL=chunk-6UNSUNJS.cjs.map
10402
+ //# sourceMappingURL=chunk-6UNSUNJS.cjs.map