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.
- package/dist/{chunk-WU7QMHPK.cjs → chunk-6UNSUNJS.cjs} +311 -287
- package/dist/{chunk-FVQKCQC3.js → chunk-G6O4SIEH.js} +311 -287
- package/dist/index.cjs +5 -5
- package/dist/index.js +1 -1
- package/dist/next.cjs +4 -4
- package/dist/next.js +1 -1
- package/dist/standalone.cjs +2 -2
- package/dist/standalone.js +1 -1
- package/package.json +3 -2
|
@@ -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
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
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(
|
|
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({
|
|
6096
|
-
|
|
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
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
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
|
|
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 &&
|
|
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 ||
|
|
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
|
-
|
|
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(
|
|
8964
|
-
|
|
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-
|
|
10378
|
-
//# sourceMappingURL=chunk-
|
|
10401
|
+
//# sourceMappingURL=chunk-6UNSUNJS.cjs.map
|
|
10402
|
+
//# sourceMappingURL=chunk-6UNSUNJS.cjs.map
|