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.
@@ -1734,6 +1734,8 @@ var STR = {
1734
1734
  loc_show: "Show captured location",
1735
1735
  loc_hide: "Hide location",
1736
1736
  loc_locate: "Locate on page",
1737
+ loc_other_page: "This one was filed on {path}, not the page you are on.",
1738
+ loc_go_there: "Go to {path}",
1737
1739
  journey_title: "Testing journey",
1738
1740
  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.",
1739
1741
  export_name_title: "Name your export",
@@ -1862,6 +1864,13 @@ var STR = {
1862
1864
  retest_not_found: "Can't find that spot on this page \u2014 open the right page first",
1863
1865
  before_label: "Before",
1864
1866
  after_label: "After",
1867
+ followup_label: "What happened this time",
1868
+ followup_add: "Say what happened",
1869
+ followup_placeholder: "What did it do this time, and how is that different from what you asked for?",
1870
+ followup_hint: "The first note stays as it is. This is added under it as round two.",
1871
+ keep_only_retest: "Keep only these {n}",
1872
+ keep_only_retest_q: "Delete the other {n}?",
1873
+ keep_only_retest_hint: "Delete every note that is not waiting to be re-tested",
1865
1874
  welcome_title: "Testing {brand}",
1866
1875
  welcome_capture: 'Click "Capture from page", then click whatever looks wrong.',
1867
1876
  welcome_say: "Say what you expected in your own words. A screenshot is taken for you.",
@@ -1882,6 +1891,7 @@ var STR = {
1882
1891
  capture_screen: "Whole screen",
1883
1892
  walk_title: "Walkthrough",
1884
1893
  walk_take_me: "Take me to {path}",
1894
+ walk_taking_you: "Taking you there\u2026",
1885
1895
  walk_reload_hint: "Reload the page instead (use this if the app didn't move)",
1886
1896
  walk_note_title: "Point {n}",
1887
1897
  walk_still_broken: "Still broken",
@@ -1932,6 +1942,8 @@ var STR = {
1932
1942
  loc_show: "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0645\u0648\u0642\u0639 \u0627\u0644\u0645\u064F\u0644\u062A\u0642\u064E\u0637",
1933
1943
  loc_hide: "\u0625\u062E\u0641\u0627\u0621 \u0627\u0644\u0645\u0648\u0642\u0639",
1934
1944
  loc_locate: "\u0625\u0638\u0647\u0627\u0631 \u0639\u0644\u0649 \u0627\u0644\u0635\u0641\u062D\u0629",
1945
+ 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.",
1946
+ loc_go_there: "\u0627\u0630\u0647\u0628 \u0625\u0644\u0649 {path}",
1935
1947
  journey_title: "\u0631\u062D\u0644\u0629 \u0627\u0644\u0627\u062E\u062A\u0628\u0627\u0631",
1936
1948
  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.",
1937
1949
  export_name_title: "\u0633\u0645\u0650\u0651 \u0645\u0644\u0641 \u0627\u0644\u062A\u0635\u062F\u064A\u0631",
@@ -2060,6 +2072,13 @@ var STR = {
2060
2072
  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
2073
  before_label: "\u0642\u0628\u0644",
2062
2074
  after_label: "\u0628\u0639\u062F",
2075
+ followup_label: "\u0645\u0627\u0630\u0627 \u062D\u062F\u062B \u0647\u0630\u0647 \u0627\u0644\u0645\u0631\u0629",
2076
+ followup_add: "\u0627\u0643\u062A\u0628 \u0645\u0627\u0630\u0627 \u062D\u062F\u062B",
2077
+ 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",
2078
+ 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.",
2079
+ keep_only_retest: "\u0623\u0628\u0642\u0650 \u0647\u0630\u0647 \u0627\u0644\u0640 {n} \u0641\u0642\u0637",
2080
+ keep_only_retest_q: "\u062D\u0630\u0641 \u0627\u0644\u0640 {n} \u0627\u0644\u0623\u062E\u0631\u0649\u061F",
2081
+ 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
2082
  welcome_title: "\u0627\u062E\u062A\u0628\u0627\u0631 {brand}",
2064
2083
  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
2084
  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.",
@@ -2080,6 +2099,7 @@ var STR = {
2080
2099
  capture_screen: "\u0627\u0644\u0634\u0627\u0634\u0629 \u0643\u0627\u0645\u0644\u0629",
2081
2100
  walk_title: "\u0627\u0644\u062C\u0648\u0644\u0629",
2082
2101
  walk_take_me: "\u062E\u0630\u0646\u064A \u0625\u0644\u0649 {path}",
2102
+ walk_taking_you: "\u062C\u0627\u0631\u064D \u0646\u0642\u0644\u0643\u2026",
2083
2103
  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)",
2084
2104
  walk_note_title: "\u0627\u0644\u0646\u0642\u0637\u0629 {n}",
2085
2105
  walk_still_broken: "\u0645\u0627 \u0632\u0627\u0644 \u0645\u0639\u0637\u0644\u064B\u0627",
@@ -2104,18 +2124,6 @@ function pick(value, lang) {
2104
2124
  if (typeof value === "string") return value;
2105
2125
  return value[lang] ?? value.en ?? "";
2106
2126
  }
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
2127
  function computeCoverage(journey, guideChecked, skipped) {
2120
2128
  const red = { total: 0, covered: 0 };
2121
2129
  const amber = { total: 0, covered: 0 };
@@ -2969,16 +2977,19 @@ function clipToPaintedArea(el, rect) {
2969
2977
  let top = rect.top;
2970
2978
  let right = rect.left + rect.width;
2971
2979
  let bottom = rect.top + rect.height;
2980
+ const doc = el.ownerDocument;
2981
+ const root2 = doc ? doc.documentElement : null;
2982
+ const body = doc ? doc.body : null;
2983
+ const rootStyle = root2 ? safeComputedStyle(root2) : null;
2984
+ const bodyStyle = body ? safeComputedStyle(body) : null;
2972
2985
  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";
2986
+ while (node && node !== root2) {
2987
+ const style = safeComputedStyle(node);
2988
+ if (!style) break;
2989
+ const propagatedX = node === body && rootStyle?.overflowX === "visible";
2990
+ const propagatedY = node === body && rootStyle?.overflowY === "visible";
2991
+ const clipsX = !propagatedX && style.overflowX !== "visible";
2992
+ const clipsY = !propagatedY && style.overflowY !== "visible";
2982
2993
  if (clipsX || clipsY) {
2983
2994
  const b = node.getBoundingClientRect();
2984
2995
  const padLeft = b.left + node.clientLeft;
@@ -2994,9 +3005,33 @@ function clipToPaintedArea(el, rect) {
2994
3005
  }
2995
3006
  node = node.parentElement;
2996
3007
  }
3008
+ const vpX = usedViewportOverflow(rootStyle?.overflowX, bodyStyle?.overflowX);
3009
+ const vpY = usedViewportOverflow(rootStyle?.overflowY, bodyStyle?.overflowY);
3010
+ if (vpX === "hidden" || vpX === "clip" || vpY === "hidden" || vpY === "clip") {
3011
+ const { vw, vh } = viewportSize();
3012
+ if (vpX === "hidden" || vpX === "clip") {
3013
+ left = Math.max(left, 0);
3014
+ right = Math.min(right, vw);
3015
+ }
3016
+ if (vpY === "hidden" || vpY === "clip") {
3017
+ top = Math.max(top, 0);
3018
+ bottom = Math.min(bottom, vh);
3019
+ }
3020
+ }
2997
3021
  if (right - left < 2 || bottom - top < 2) return rect;
2998
3022
  return { left, top, width: right - left, height: bottom - top };
2999
3023
  }
3024
+ function safeComputedStyle(node) {
3025
+ try {
3026
+ return getComputedStyle(node);
3027
+ } catch {
3028
+ return null;
3029
+ }
3030
+ }
3031
+ function usedViewportOverflow(rootValue, bodyValue) {
3032
+ if (rootValue && rootValue !== "visible") return rootValue;
3033
+ return bodyValue || "visible";
3034
+ }
3000
3035
  function intersectViewport(rect) {
3001
3036
  const { vw, vh } = viewportSize();
3002
3037
  const left = Math.max(0, rect.left);
@@ -3191,6 +3226,16 @@ function noteToMarkdown(note, opts) {
3191
3226
  }
3192
3227
  lines.push("");
3193
3228
  lines.push(oneLine(note.description) ? note.description.trim() : "_(no description)_");
3229
+ if (note.followUp && oneLine(note.followUp)) {
3230
+ lines.push("");
3231
+ lines.push(
3232
+ "> **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."
3233
+ );
3234
+ lines.push("");
3235
+ lines.push(`**What happened this time**${note.followUpAt ? ` (${oneLine(note.followUpAt)})` : ""}`);
3236
+ lines.push("");
3237
+ lines.push(note.followUp.trim());
3238
+ }
3194
3239
  const recordedSteps = note.context?.steps ?? [];
3195
3240
  if (recordedSteps.length) {
3196
3241
  const t0 = Date.parse(note.timestamp) || recordedSteps[recordedSteps.length - 1].t;
@@ -3277,7 +3322,7 @@ function fmtPct(n, d) {
3277
3322
  }
3278
3323
  function summariseSession(notes, stamp) {
3279
3324
  if (!notes.length) return "No points captured.";
3280
- let bugs = 0, enhancements = 0, designs = 0, verified = 0, awaiting = 0;
3325
+ let bugs = 0, enhancements = 0, designs = 0, verified = 0, awaiting = 0, secondRound = 0;
3281
3326
  const pages = /* @__PURE__ */ new Set();
3282
3327
  let earliest = Number.POSITIVE_INFINITY;
3283
3328
  let latest = 0;
@@ -3289,6 +3334,7 @@ function summariseSession(notes, stamp) {
3289
3334
  const status = n.status ?? "open";
3290
3335
  if (status === "verified") verified++;
3291
3336
  else if (status === "fixed") awaiting++;
3337
+ if (n.followUp && n.followUp.trim()) secondRound++;
3292
3338
  pages.add((n.route || "/").split("?")[0]);
3293
3339
  const t = Date.parse(n.timestamp);
3294
3340
  if (Number.isFinite(t)) {
@@ -3304,6 +3350,7 @@ function summariseSession(notes, stamp) {
3304
3350
  if (designs) parts.push(`${designs} design`);
3305
3351
  if (verified) parts.push(`${verified} verified`);
3306
3352
  if (awaiting) parts.push(`${awaiting} awaiting re-test`);
3353
+ if (secondRound) parts.push(`${secondRound} back for a second round`);
3307
3354
  parts.push(`${pages.size} page${pages.size === 1 ? "" : "s"}`);
3308
3355
  if (Number.isFinite(earliest) && latest > earliest) {
3309
3356
  const minutes = Math.max(1, Math.round((latest - earliest) / 6e4));
@@ -3358,6 +3405,14 @@ The tester wants something added or changed: a new section, a new feature, a dif
3358
3405
 
3359
3406
  Push back only when you have a real reason: it would break something, or it conflicts with an invariant below. Then say so plainly and propose the alternative you would build instead.
3360
3407
 
3408
+ ### A point with a "round 2" on it
3409
+
3410
+ 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.
3411
+
3412
+ 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.
3413
+
3414
+ 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.
3415
+
3361
3416
  ### When the tag and the words disagree
3362
3417
 
3363
3418
  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.
@@ -4162,6 +4217,13 @@ var DENSE_NOTES_KEY = "denseNotes";
4162
4217
  var PANEL_OPEN_KEY = "panelOpen";
4163
4218
  var ACTIVE_TAB_KEY = "activeTab";
4164
4219
  var WALK_KEY = "walk";
4220
+ var SOFT_NAV_PROOF_MS = 700;
4221
+ function pageSignature() {
4222
+ if (typeof document === "undefined" || !document.body) return "";
4223
+ const body = document.body;
4224
+ const text = body.textContent || "";
4225
+ return [document.title, body.childElementCount, text.length, text.slice(0, 200)].join("\0");
4226
+ }
4165
4227
  var GUIDE_SKIPPED_KEY = "guideSkipped";
4166
4228
  var NOTICE_QUEUE_CAP = 3;
4167
4229
  var NOTICE_DURATION_INFO = 4e3;
@@ -4602,6 +4664,11 @@ function QaProvider({
4602
4664
  }
4603
4665
  if (patch.severity !== void 0) updated.severity = patch.severity;
4604
4666
  if (patch.status !== void 0) updated.status = patch.status;
4667
+ if (patch.followUp !== void 0) {
4668
+ const text = patch.followUp.trim();
4669
+ updated.followUp = text || void 0;
4670
+ updated.followUpAt = text ? nowIso() : void 0;
4671
+ }
4605
4672
  applyNotes((prev) => prev.map((n) => n.id === id ? updated : n));
4606
4673
  const persisted = await idb.put(updated);
4607
4674
  if (!persisted) {
@@ -5193,16 +5260,23 @@ function QaProvider({
5193
5260
  const walkNavigate = useCallback((path, hard = false) => {
5194
5261
  if (typeof window === "undefined" || !path) return;
5195
5262
  const target = path.startsWith("/") ? path : `/${path}`;
5196
- if (hard) {
5263
+ if (hard || target === window.location.pathname) {
5197
5264
  window.location.assign(target);
5198
5265
  return;
5199
5266
  }
5267
+ const before = pageSignature();
5200
5268
  try {
5201
- window.history.pushState({}, "", target);
5202
- window.dispatchEvent(new PopStateEvent("popstate"));
5269
+ window.history.pushState(window.history.state, "", target);
5270
+ window.dispatchEvent(new PopStateEvent("popstate", { state: window.history.state }));
5203
5271
  } catch {
5204
5272
  window.location.assign(target);
5273
+ return;
5205
5274
  }
5275
+ window.setTimeout(() => {
5276
+ if (window.location.pathname !== target) return;
5277
+ if (pageSignature() !== before) return;
5278
+ window.location.assign(target);
5279
+ }, SOFT_NAV_PROOF_MS);
5206
5280
  }, []);
5207
5281
  useEffect(() => {
5208
5282
  if (typeof window === "undefined") return;
@@ -6085,11 +6159,17 @@ function flashLocate(target) {
6085
6159
  paint(rect);
6086
6160
  }
6087
6161
  }
6088
- function LocationReveal({ target }) {
6089
- const { t } = useQa();
6162
+ function LocationReveal({
6163
+ target,
6164
+ route
6165
+ }) {
6166
+ const { t, walkNavigate } = useQa();
6090
6167
  const [open, setOpen] = useState(false);
6091
6168
  if (!target) return null;
6092
6169
  const r = target.rect;
6170
+ const notePath = (route || "").split("?")[0].split("#")[0];
6171
+ const here = typeof window === "undefined" ? "" : window.location.pathname;
6172
+ const elsewhere = !!notePath && notePath !== here;
6093
6173
  return /* @__PURE__ */ jsxs("div", { className: "qa-rounded-lg qa-border qa-border-subtle qa-bg-2", children: [
6094
6174
  /* @__PURE__ */ jsxs("div", { className: "qa-flex qa-items-center qa-gap-1.5 qa-px-2 qa-py-1.5 qa-text-11", children: [
6095
6175
  /* @__PURE__ */ jsx(Icon, { name: "CheckCircle2", size: 14, className: "qa-text-success" }),
@@ -6153,7 +6233,21 @@ function LocationReveal({ target }) {
6153
6233
  "\xD7",
6154
6234
  Math.round(r.height)
6155
6235
  ] }),
6156
- /* @__PURE__ */ jsxs(
6236
+ elsewhere ? /* @__PURE__ */ jsxs(Fragment, { children: [
6237
+ /* @__PURE__ */ jsx("p", { className: "qa-mt-1 qa-mb-1 qa-m-0 qa-text-lo", children: t("loc_other_page", { path: notePath }) }),
6238
+ /* @__PURE__ */ jsxs(
6239
+ "button",
6240
+ {
6241
+ onClick: () => walkNavigate(notePath),
6242
+ 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",
6243
+ style: { border: "none", cursor: "pointer" },
6244
+ children: [
6245
+ /* @__PURE__ */ jsx(Icon, { name: "MapPinned", size: 12 }),
6246
+ t("loc_go_there", { path: notePath })
6247
+ ]
6248
+ }
6249
+ )
6250
+ ] }) : /* @__PURE__ */ jsxs(
6157
6251
  "button",
6158
6252
  {
6159
6253
  onClick: () => flashLocate(target),
@@ -6597,6 +6691,8 @@ function NoteItem({
6597
6691
  const thumbUrl = useObjectUrl(editing ? img ?? void 0 : note.screenshot);
6598
6692
  const afterUrl = useObjectUrl(note.afterScreenshot);
6599
6693
  const [retesting, setRetesting] = useState(false);
6694
+ const [editingFollowUp, setEditingFollowUp] = useState(false);
6695
+ const [followUpDraft, setFollowUpDraft] = useState(note.followUp ?? "");
6600
6696
  const [open, setOpen] = useState(false);
6601
6697
  const severity = note.severity ?? "bug";
6602
6698
  const status = note.status ?? "open";
@@ -6869,7 +6965,7 @@ function NoteItem({
6869
6965
  /* @__PURE__ */ jsx(Icon, { name: "MapPin", size: 12, className: "qa-shrink-0" }),
6870
6966
  /* @__PURE__ */ jsx("span", { className: "qa-truncate qa-dir-ltr", title: note.url, children: note.route })
6871
6967
  ] }),
6872
- note.target && /* @__PURE__ */ jsx(LocationReveal, { target: note.target }),
6968
+ note.target && /* @__PURE__ */ jsx(LocationReveal, { target: note.target, route: note.route }),
6873
6969
  contextEventCount > 0 && /* @__PURE__ */ jsx("div", { children: t("context_attached", { n: contextEventCount }) }),
6874
6970
  stepCount > 0 && /* @__PURE__ */ jsxs("div", { className: "qa-flex qa-items-center qa-gap-1", children: [
6875
6971
  /* @__PURE__ */ jsx(Icon, { name: "ClipboardList", size: 12, className: "qa-shrink-0" }),
@@ -6930,7 +7026,96 @@ function NoteItem({
6930
7026
  style: { borderColor: "var(--qa-success)" }
6931
7027
  }
6932
7028
  )
6933
- ] })
7029
+ ] }),
7030
+ (status === "fixed" || note.followUp) && /* @__PURE__ */ jsx(
7031
+ "div",
7032
+ {
7033
+ className: "qa-mt-2 qa-rounded-lg qa-border qa-border-subtle qa-bg-2 qa-p-2",
7034
+ "data-qa-followup": "true",
7035
+ children: editingFollowUp ? /* @__PURE__ */ jsxs(Fragment, { children: [
7036
+ /* @__PURE__ */ jsx("label", { className: "qa-text-10 qa-text-lo", htmlFor: `qa-fu-${note.id}`, children: t("followup_label") }),
7037
+ /* @__PURE__ */ jsx(
7038
+ "textarea",
7039
+ {
7040
+ id: `qa-fu-${note.id}`,
7041
+ value: followUpDraft,
7042
+ onChange: (e) => setFollowUpDraft(e.target.value),
7043
+ placeholder: t("followup_placeholder"),
7044
+ rows: 3,
7045
+ 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",
7046
+ style: { outline: "none", resize: "vertical" }
7047
+ }
7048
+ ),
7049
+ /* @__PURE__ */ jsx("p", { className: "qa-mt-1 qa-text-10 qa-text-lo", children: t("followup_hint") }),
7050
+ /* @__PURE__ */ jsxs("div", { className: "qa-mt-1.5 qa-flex qa-gap-2", children: [
7051
+ /* @__PURE__ */ jsxs(
7052
+ "button",
7053
+ {
7054
+ type: "button",
7055
+ onClick: () => {
7056
+ void updateNote(note.id, { followUp: followUpDraft });
7057
+ setEditingFollowUp(false);
7058
+ },
7059
+ 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",
7060
+ style: { border: "none", cursor: "pointer" },
7061
+ children: [
7062
+ /* @__PURE__ */ jsx(Icon, { name: "Check", size: 12 }),
7063
+ t("save")
7064
+ ]
7065
+ }
7066
+ ),
7067
+ /* @__PURE__ */ jsx(
7068
+ "button",
7069
+ {
7070
+ type: "button",
7071
+ onClick: () => {
7072
+ setFollowUpDraft(note.followUp ?? "");
7073
+ setEditingFollowUp(false);
7074
+ },
7075
+ className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-11 qa-text-mid",
7076
+ style: { background: "transparent", cursor: "pointer" },
7077
+ children: t("cancel")
7078
+ }
7079
+ )
7080
+ ] })
7081
+ ] }) : note.followUp ? /* @__PURE__ */ jsxs(Fragment, { children: [
7082
+ /* @__PURE__ */ jsxs("div", { className: "qa-flex qa-items-center qa-gap-1", children: [
7083
+ /* @__PURE__ */ jsx(Icon, { name: "RotateCcw", size: 11, className: "qa-shrink-0 qa-text-warn" }),
7084
+ /* @__PURE__ */ jsx("span", { className: "qa-text-10 qa-text-warn", children: t("followup_label") }),
7085
+ /* @__PURE__ */ jsx(
7086
+ "button",
7087
+ {
7088
+ type: "button",
7089
+ onClick: () => {
7090
+ setFollowUpDraft(note.followUp ?? "");
7091
+ setEditingFollowUp(true);
7092
+ },
7093
+ "aria-label": t("edit"),
7094
+ className: "qa-tap-icon qa-ms-auto qa-text-lo",
7095
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7096
+ children: /* @__PURE__ */ jsx(Icon, { name: "Pencil", size: 11 })
7097
+ }
7098
+ )
7099
+ ] }),
7100
+ /* @__PURE__ */ jsx("p", { className: "qa-mt-1 qa-whitespace-pre-wrap qa-break-words qa-text-xs qa-text-hi", children: note.followUp })
7101
+ ] }) : /* @__PURE__ */ jsxs(
7102
+ "button",
7103
+ {
7104
+ type: "button",
7105
+ onClick: () => {
7106
+ setFollowUpDraft("");
7107
+ setEditingFollowUp(true);
7108
+ },
7109
+ className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-text-11 qa-font-medium qa-text-accent",
7110
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7111
+ children: [
7112
+ /* @__PURE__ */ jsx(Icon, { name: "Plus", size: 12 }),
7113
+ t("followup_add")
7114
+ ]
7115
+ }
7116
+ )
7117
+ }
7118
+ )
6934
7119
  ] })
6935
7120
  ]
6936
7121
  }
@@ -7163,8 +7348,11 @@ function FilterChip({ chip }) {
7163
7348
  );
7164
7349
  }
7165
7350
  function NoteFilterBar() {
7166
- const { filter, setFilter, noteCounts, notes, t } = useQa();
7351
+ const { filter, setFilter, noteCounts, notes, deleteNotes, t } = useQa();
7352
+ const [confirmPrune, setConfirmPrune] = useState(false);
7167
7353
  if (notes.length < 3) return null;
7354
+ const others = notes.filter((n) => (n.status ?? "open") !== "fixed");
7355
+ const showPrune = filter.status === "fixed" && noteCounts.fixed > 0 && others.length > 0;
7168
7356
  const sev = (key) => () => setFilter({ severity: filter.severity === key ? "all" : key });
7169
7357
  const stat = (key) => () => setFilter({ status: filter.status === key ? "all" : key });
7170
7358
  const chips = [
@@ -7269,7 +7457,48 @@ function NoteFilterBar() {
7269
7457
  ]
7270
7458
  }
7271
7459
  ),
7272
- /* @__PURE__ */ jsx("div", { className: "qa-flex qa-flex-wrap qa-gap-1", children: chips.map((chip) => /* @__PURE__ */ jsx(FilterChip, { chip }, chip.key)) })
7460
+ /* @__PURE__ */ jsx("div", { className: "qa-flex qa-flex-wrap qa-gap-1", children: chips.map((chip) => /* @__PURE__ */ jsx(FilterChip, { chip }, chip.key)) }),
7461
+ showPrune && /* @__PURE__ */ jsx("div", { className: "qa-text-11", "data-qa-prune": "true", children: confirmPrune ? /* @__PURE__ */ jsxs("span", { className: "qa-text-mid", children: [
7462
+ t("keep_only_retest_q", { n: others.length }),
7463
+ " ",
7464
+ /* @__PURE__ */ jsx(
7465
+ "button",
7466
+ {
7467
+ type: "button",
7468
+ onClick: () => {
7469
+ void deleteNotes(others.map((n) => n.id));
7470
+ setConfirmPrune(false);
7471
+ },
7472
+ className: "qa-font-semibold qa-text-danger qa-tap",
7473
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7474
+ children: t("yes")
7475
+ }
7476
+ ),
7477
+ " / ",
7478
+ /* @__PURE__ */ jsx(
7479
+ "button",
7480
+ {
7481
+ type: "button",
7482
+ onClick: () => setConfirmPrune(false),
7483
+ className: "qa-text-accent qa-tap",
7484
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7485
+ children: t("no")
7486
+ }
7487
+ )
7488
+ ] }) : /* @__PURE__ */ jsxs(
7489
+ "button",
7490
+ {
7491
+ type: "button",
7492
+ onClick: () => setConfirmPrune(true),
7493
+ title: t("keep_only_retest_hint"),
7494
+ className: "qa-tap qa-inline-flex qa-items-center qa-gap-1 qa-text-lo",
7495
+ style: { background: "transparent", border: "none", cursor: "pointer" },
7496
+ children: [
7497
+ /* @__PURE__ */ jsx(Icon, { name: "Trash", size: 11 }),
7498
+ t("keep_only_retest", { n: noteCounts.fixed })
7499
+ ]
7500
+ }
7501
+ ) })
7273
7502
  ] });
7274
7503
  }
7275
7504
  function Section({
@@ -7805,249 +8034,9 @@ function CredentialsSection() {
7805
8034
  })
7806
8035
  ] });
7807
8036
  }
7808
- var keyOf = (id, path) => `${id}::${path}`;
7809
- var DEFAULT_LANE_COLOR = "#4D9CFF";
7810
- function Lane({
7811
- group,
7812
- checked,
7813
- toggle,
7814
- pick: pick2,
7815
- onWalkFrom
7816
- }) {
7817
- const { lang, t, guideFailed, evidenceByStep } = useQa();
7818
- const { id, color = DEFAULT_LANE_COLOR, steps: steps2 } = group;
7819
- const done = steps2.filter((s) => checked.has(keyOf(id, s.path))).length;
7820
- const pct = steps2.length > 0 ? Math.round(done / steps2.length * 100) : 0;
7821
- const uncoveredRedCount = steps2.filter(
7822
- (s) => s.risk === "red" && !checked.has(keyOf(id, s.path))
7823
- ).length;
7824
- return /* @__PURE__ */ jsxs("div", { className: "qa-rounded-xl qa-border qa-border-subtle qa-bg-2 qa-p-3 qa-elev-1", children: [
7825
- /* @__PURE__ */ jsxs("div", { className: "qa-mb-2 qa-flex qa-items-center qa-gap-2", children: [
7826
- /* @__PURE__ */ jsx(
7827
- "span",
7828
- {
7829
- className: "qa-h-2.5 qa-w-2.5 qa-rounded-full",
7830
- style: { background: color }
7831
- }
7832
- ),
7833
- /* @__PURE__ */ jsx("span", { className: "qa-text-sm qa-font-bold qa-text-hi", children: pick2(group.role) }),
7834
- /* @__PURE__ */ jsxs("span", { className: "qa-ms-auto qa-text-11 qa-font-medium qa-text-slate-400", children: [
7835
- done,
7836
- "/",
7837
- steps2.length
7838
- ] }),
7839
- uncoveredRedCount > 0 && /* @__PURE__ */ jsx(
7840
- "span",
7841
- {
7842
- className: "qa-bg-danger-tint qa-text-danger qa-rounded qa-px-1 qa-text-10 qa-font-medium",
7843
- title: lang === "ar" ? `${uncoveredRedCount} \u0645\u0646\u0637\u0642\u0629 \u062D\u0645\u0631\u0627\u0621 \u063A\u064A\u0631 \u0645\u063A\u0637\u0627\u0629` : `${uncoveredRedCount} uncovered red zone(s)`,
7844
- children: lang === "ar" ? `\u0623\u062D\u0645\u0631: ${uncoveredRedCount}` : `red: ${uncoveredRedCount}`
7845
- }
7846
- )
7847
- ] }),
7848
- /* @__PURE__ */ jsx(
7849
- "div",
7850
- {
7851
- className: "qa-mb-3 qa-h-1.5 qa-overflow-hidden qa-rounded-full",
7852
- style: { background: `${color}22` },
7853
- children: /* @__PURE__ */ jsx(
7854
- "div",
7855
- {
7856
- className: "qa-h-full qa-rounded-full qa-transition-all",
7857
- style: { width: `${pct}%`, background: color }
7858
- }
7859
- )
7860
- }
7861
- ),
7862
- /* @__PURE__ */ jsxs("ol", { className: "qa-relative qa-ms-1.5", children: [
7863
- /* @__PURE__ */ jsx(
7864
- "span",
7865
- {
7866
- className: "qa-absolute qa-top-1 qa-bottom-0 qa-w-px",
7867
- style: { insetInlineStart: "7px", background: `${color}40`, bottom: "4px" }
7868
- }
7869
- ),
7870
- steps2.map((s, i) => {
7871
- const k = keyOf(id, s.path);
7872
- const on = checked.has(k);
7873
- const failed = guideFailed.has(k);
7874
- const evidence = evidenceByStep.get(k);
7875
- const evidenceCount = evidence ? evidence.length : 0;
7876
- const riskColor = s.risk ? RISK_COLORS[s.risk] : RISK_COLORS.none;
7877
- const dotTitle = !s.risk ? lang === "ar" ? "\u0644\u0645 \u064A\u062A\u0645 \u062A\u0642\u064A\u064A\u0645 \u0627\u0644\u0645\u062E\u0627\u0637\u0631 \u0628\u0639\u062F" : "not graded yet" : s.riskWhy ?? s.risk;
7878
- return /* @__PURE__ */ jsxs("li", { className: "qa-relative qa-mb-2 qa-last-mb-0", children: [
7879
- /* @__PURE__ */ jsx(
7880
- "button",
7881
- {
7882
- onClick: () => onWalkFrom(k),
7883
- title: t("walk_take_me", { path: s.path }),
7884
- "aria-label": t("walk_take_me", { path: s.path }),
7885
- className: "qa-tap-icon qa-absolute qa-rounded-lg qa-text-mid qa-hover-text-slate-600",
7886
- style: {
7887
- top: 0,
7888
- insetInlineEnd: 0,
7889
- background: "transparent",
7890
- border: "none",
7891
- cursor: "pointer",
7892
- zIndex: 2
7893
- },
7894
- children: /* @__PURE__ */ jsx(Icon, { name: "Play", size: 12 })
7895
- }
7896
- ),
7897
- /* @__PURE__ */ jsxs(
7898
- "button",
7899
- {
7900
- onClick: () => toggle(k),
7901
- className: `qa-flex qa-w-full qa-items-start qa-gap-2.5 qa-rounded-lg qa-p-1 qa-text-start qa-hover-bg-black-3${failed ? " qa-bg-danger-tint" : ""}`,
7902
- style: { background: failed ? void 0 : "transparent", border: "none", cursor: "pointer" },
7903
- children: [
7904
- /* @__PURE__ */ jsxs(
7905
- "span",
7906
- {
7907
- className: "qa-relative qa-z-1 qa-mt-0.5 qa-flex qa-h-4 qa-w-4 qa-shrink-0 qa-items-center qa-justify-center qa-rounded-full qa-border-2 qa-transition",
7908
- style: {
7909
- borderColor: failed ? "var(--qa-danger)" : color,
7910
- background: on ? color : failed ? "var(--qa-danger-tint)" : "var(--qa-surface-1)",
7911
- zIndex: 1
7912
- },
7913
- children: [
7914
- on && /* @__PURE__ */ jsx(Icon, { name: "Check", size: 10, strokeWidth: 3, className: "qa-text-white" }),
7915
- !on && failed && /* @__PURE__ */ jsx(Icon, { name: "AlertTriangle", size: 9, strokeWidth: 2.5, className: "qa-text-danger" })
7916
- ]
7917
- }
7918
- ),
7919
- /* @__PURE__ */ jsxs("span", { className: "qa-min-w-0", children: [
7920
- /* @__PURE__ */ jsxs("span", { className: "qa-flex qa-items-center qa-gap-1", children: [
7921
- /* @__PURE__ */ jsx(
7922
- "code",
7923
- {
7924
- className: "qa-rounded qa-px-1 qa-text-11 qa-font-semibold qa-dir-ltr qa-text-hi",
7925
- style: {
7926
- background: failed ? "var(--qa-danger-tint)" : `${color}14`,
7927
- textDecoration: on ? "line-through" : "none",
7928
- opacity: on ? 0.55 : 1
7929
- },
7930
- children: s.path
7931
- }
7932
- ),
7933
- /* @__PURE__ */ jsx(
7934
- "span",
7935
- {
7936
- className: "qa-inline-block qa-rounded-full qa-shrink-0",
7937
- style: {
7938
- width: "6px",
7939
- height: "6px",
7940
- background: riskColor,
7941
- flexShrink: 0
7942
- },
7943
- title: dotTitle
7944
- }
7945
- )
7946
- ] }),
7947
- /* @__PURE__ */ jsx(
7948
- "span",
7949
- {
7950
- className: "qa-mt-0.5 qa-block qa-text-11 qa-leading-relaxed qa-text-slate-500",
7951
- style: { opacity: on ? 0.5 : 1 },
7952
- children: pick2(s.what)
7953
- }
7954
- ),
7955
- evidenceCount > 0 && /* @__PURE__ */ jsx("span", { className: "qa-mt-1 qa-inline-flex qa-items-center qa-rounded qa-bg-accent-tint qa-text-accent qa-px-1 qa-text-10 qa-font-medium", children: t("evidence_n", { n: evidenceCount }) }),
7956
- evidenceCount === 0 && on && /* @__PURE__ */ jsx("span", { className: "qa-mt-1 qa-inline-flex qa-items-center qa-rounded qa-bg-warn-tint qa-text-warn qa-px-1 qa-text-10 qa-font-medium", children: t("no_evidence") })
7957
- ] })
7958
- ]
7959
- }
7960
- )
7961
- ] }, `${k}-${i}`);
7962
- })
7963
- ] })
7964
- ] });
7965
- }
7966
- function GuideSection() {
7967
- const {
7968
- guideChecked,
7969
- toggleGuide,
7970
- t,
7971
- journey,
7972
- journeyIsGeneric,
7973
- pick: pick2,
7974
- lang,
7975
- startTestAlong,
7976
- startWalk,
7977
- testAlongSteps
7978
- } = useQa();
7979
- const all = journey.flatMap((g) => g.steps.map((s) => keyOf(g.id, s.path)));
7980
- const done = all.filter((k) => guideChecked.has(k)).length;
7981
- const pct = all.length > 0 ? Math.round(done / all.length * 100) : 0;
7982
- const coverage = computeCoverage(journey, guideChecked);
7983
- return /* @__PURE__ */ jsxs("div", { className: "qa-space-y-3", children: [
7984
- journeyIsGeneric && /* @__PURE__ */ jsx(
7985
- "p",
7986
- {
7987
- className: "qa-m-0 qa-rounded-xl qa-border qa-border-dashed qa-border-subtle qa-p-2 qa-text-10 qa-text-mid qa-leading-relaxed",
7988
- "data-qa-generic-plan": "true",
7989
- children: t("journey_generic")
7990
- }
7991
- ),
7992
- /* @__PURE__ */ jsxs("div", { className: "qa-rounded-xl qa-border qa-border-accent qa-bg-accent-tint qa-p-3 qa-elev-1", children: [
7993
- coverage.red.total > 0 && /* @__PURE__ */ jsxs("div", { className: "qa-mb-1 qa-flex qa-items-center qa-gap-1.5 qa-text-11", children: [
7994
- /* @__PURE__ */ jsx("span", { className: "qa-bg-danger-tint qa-text-danger qa-rounded qa-px-1 qa-font-bold", children: lang === "ar" ? "\u0623\u062D\u0645\u0631" : "RED" }),
7995
- /* @__PURE__ */ jsxs("span", { className: "qa-dir-ltr qa-font-semibold qa-text-hi", children: [
7996
- coverage.red.covered,
7997
- "/",
7998
- coverage.red.total,
7999
- " ",
8000
- lang === "ar" ? "\u0645\u063A\u0637\u0649" : "covered"
8001
- ] })
8002
- ] }),
8003
- /* @__PURE__ */ jsxs("div", { className: "qa-flex qa-items-center qa-justify-between qa-text-sm qa-font-semibold qa-text-hi", children: [
8004
- /* @__PURE__ */ jsx("span", { children: t("journey_title") }),
8005
- /* @__PURE__ */ jsxs("span", { className: "qa-dir-ltr", children: [
8006
- done,
8007
- "/",
8008
- all.length,
8009
- " \xB7 ",
8010
- pct,
8011
- "%"
8012
- ] })
8013
- ] }),
8014
- /* @__PURE__ */ jsx("div", { className: "qa-mt-2 qa-h-2 qa-overflow-hidden qa-rounded-full qa-bg-3", children: /* @__PURE__ */ jsx(
8015
- "div",
8016
- {
8017
- className: "qa-h-full qa-rounded-full qa-bg-accent qa-transition-all",
8018
- style: { width: `${pct}%` }
8019
- }
8020
- ) }),
8021
- /* @__PURE__ */ jsx("div", { className: "qa-mt-2 qa-flex qa-justify-end", children: /* @__PURE__ */ jsxs(
8022
- "button",
8023
- {
8024
- type: "button",
8025
- onClick: startTestAlong,
8026
- disabled: all.length === 0,
8027
- className: "qa-tap qa-inline-flex qa-items-center qa-gap-1.5 qa-rounded-full qa-bg-accent qa-border-0 qa-cursor-pointer qa-px-3 qa-py-1 qa-text-xs qa-font-semibold qa-focus-ring",
8028
- children: [
8029
- /* @__PURE__ */ jsx(Icon, { name: "Play", size: 13 }),
8030
- t("start_walkthrough")
8031
- ]
8032
- }
8033
- ) })
8034
- ] }),
8035
- journey.map((g) => /* @__PURE__ */ jsx(
8036
- Lane,
8037
- {
8038
- group: g,
8039
- checked: guideChecked,
8040
- toggle: toggleGuide,
8041
- onWalkFrom: (key) => {
8042
- const at = testAlongSteps.findIndex((step) => step.key === key);
8043
- startWalk("plan", at === -1 ? 0 : at);
8044
- },
8045
- pick: pick2
8046
- },
8047
- g.id
8048
- ))
8049
- ] });
8050
- }
8037
+
8038
+ // src/lib/features.ts
8039
+ var GUIDE_TAB_ENABLED = false;
8051
8040
  function isCoarsePointer() {
8052
8041
  if (typeof window === "undefined") return false;
8053
8042
  try {
@@ -8071,11 +8060,15 @@ function useCoarsePointer() {
8071
8060
  }, []);
8072
8061
  return coarse;
8073
8062
  }
8074
- var TABS = [
8063
+ var ALL_TABS = [
8075
8064
  { key: "notes", labelKey: "tab_notes", icon: "StickyNote" },
8076
8065
  { key: "logins", labelKey: "tab_logins", icon: "KeyRound" },
8077
8066
  { key: "guide", labelKey: "tab_guide", icon: "Map" }
8078
8067
  ];
8068
+ var TABS = ALL_TABS.filter((tab) => tab.key !== "guide" || GUIDE_TAB_ENABLED);
8069
+ function visibleTab(tab) {
8070
+ return tab === "guide" && !GUIDE_TAB_ENABLED ? "notes" : tab;
8071
+ }
8079
8072
  function todayName() {
8080
8073
  return `qa-notes-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
8081
8074
  }
@@ -8110,7 +8103,7 @@ var PANEL_TRANSITION_WITH_LIFT = "opacity 200ms cubic-bezier(0.4,0,0.2,1), trans
8110
8103
  function QaPanel() {
8111
8104
  const {
8112
8105
  isOpen,
8113
- activeTab,
8106
+ activeTab: storedTab,
8114
8107
  setActiveTab,
8115
8108
  notes,
8116
8109
  exportZip,
@@ -8141,6 +8134,7 @@ function QaPanel() {
8141
8134
  panelCollapsed,
8142
8135
  setPanelCollapsed
8143
8136
  } = useQa();
8137
+ const activeTab = visibleTab(storedTab);
8144
8138
  const [confirmClear, setConfirmClear] = useState(false);
8145
8139
  const [naming, setNaming] = useState(false);
8146
8140
  const [filename, setFilename] = useState("");
@@ -8508,7 +8502,7 @@ function QaPanel() {
8508
8502
  ) })
8509
8503
  ] }),
8510
8504
  !simpleMode && activeTab === "logins" && /* @__PURE__ */ jsx(CredentialsSection, {}),
8511
- !simpleMode && activeTab === "guide" && /* @__PURE__ */ jsx(GuideSection, {})
8505
+ !simpleMode && GUIDE_TAB_ENABLED
8512
8506
  ] })
8513
8507
  ] }),
8514
8508
  settingsOpen && /* @__PURE__ */ jsx(SettingsSheet, { onClose: () => setSettingsOpen(false) }),
@@ -8715,6 +8709,20 @@ var RISK_COLOR = {
8715
8709
  amber: "var(--qa-warn)",
8716
8710
  green: "var(--qa-success)"
8717
8711
  };
8712
+ function useCurrentPath() {
8713
+ const [path, setPath] = useState(() => typeof window === "undefined" ? "" : window.location.pathname);
8714
+ useEffect(() => {
8715
+ if (typeof window === "undefined") return;
8716
+ const sync = () => setPath(window.location.pathname);
8717
+ window.addEventListener("popstate", sync);
8718
+ window.addEventListener("hashchange", sync);
8719
+ return () => {
8720
+ window.removeEventListener("popstate", sync);
8721
+ window.removeEventListener("hashchange", sync);
8722
+ };
8723
+ }, []);
8724
+ return path;
8725
+ }
8718
8726
  function WalkHud() {
8719
8727
  const {
8720
8728
  dir,
@@ -8734,10 +8742,13 @@ function WalkHud() {
8734
8742
  startCapture,
8735
8743
  updateNote,
8736
8744
  retestNote,
8737
- notes
8745
+ notes,
8746
+ notesLoading
8738
8747
  } = useQa();
8739
8748
  const index = Math.min(walk.index, Math.max(0, walkStops.length - 1));
8740
8749
  const stop = walkStops[index];
8750
+ const currentPath = useCurrentPath();
8751
+ const [going, setGoing] = useState(false);
8741
8752
  const backIcon = dir === "rtl" ? "ChevronRight" : "ChevronLeft";
8742
8753
  const nextIcon = dir === "rtl" ? "ChevronLeft" : "ChevronRight";
8743
8754
  useEffect(() => {
@@ -8751,8 +8762,9 @@ function WalkHud() {
8751
8762
  if (latest?.target) flashLocate(latest.target);
8752
8763
  }, [index, walk.source]);
8753
8764
  useEffect(() => {
8765
+ if (notesLoading) return;
8754
8766
  if (walk.active && walkStops.length === 0) exitWalk();
8755
- }, [walk.active, walkStops.length, exitWalk]);
8767
+ }, [walk.active, walkStops.length, exitWalk, notesLoading]);
8756
8768
  if (!walk.active || !stop) return null;
8757
8769
  const atLast = index >= walkStops.length - 1;
8758
8770
  let title;
@@ -8913,7 +8925,7 @@ function WalkHud() {
8913
8925
  )
8914
8926
  ] });
8915
8927
  }
8916
- const onThisPage = stop.path === ANY_PAGE || typeof window !== "undefined" && stop.path === window.location.pathname;
8928
+ const onThisPage = stop.path === ANY_PAGE || stop.path === currentPath;
8917
8929
  return /* @__PURE__ */ jsxs(
8918
8930
  "div",
8919
8931
  {
@@ -8949,12 +8961,24 @@ function WalkHud() {
8949
8961
  "button",
8950
8962
  {
8951
8963
  type: "button",
8952
- onClick: () => walkNavigate(stop.path),
8964
+ disabled: going,
8965
+ onClick: () => {
8966
+ setGoing(true);
8967
+ walkNavigate(stop.path);
8968
+ window.setTimeout(() => setGoing(false), 900);
8969
+ },
8953
8970
  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",
8954
- style: { border: "none", cursor: "pointer" },
8971
+ style: { border: "none", cursor: going ? "default" : "pointer" },
8955
8972
  children: [
8956
- /* @__PURE__ */ jsx(Icon, { name: "MapPinned", size: 13 }),
8957
- t("walk_take_me", { path: stop.path })
8973
+ /* @__PURE__ */ jsx(
8974
+ Icon,
8975
+ {
8976
+ name: going ? "Loader2" : "MapPinned",
8977
+ size: 13,
8978
+ className: going ? "qa-animate-spin" : void 0
8979
+ }
8980
+ ),
8981
+ going ? t("walk_taking_you") : t("walk_take_me", { path: stop.path })
8958
8982
  ]
8959
8983
  }
8960
8984
  ),
@@ -10365,5 +10389,5 @@ function Qapture({ config }) {
10365
10389
  }
10366
10390
 
10367
10391
  export { Qapture, deleteQaDatabase, initQaStudio };
10368
- //# sourceMappingURL=chunk-FVQKCQC3.js.map
10369
- //# sourceMappingURL=chunk-FVQKCQC3.js.map
10392
+ //# sourceMappingURL=chunk-G6O4SIEH.js.map
10393
+ //# sourceMappingURL=chunk-G6O4SIEH.js.map