qapture2 0.7.0 → 0.7.2
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/README.md +59 -5
- package/dist/{chunk-UOUFJCPF.cjs → chunk-CIFJMD2B.cjs} +603 -50
- package/dist/{chunk-QBBWY3BF.js → chunk-NVFFN7SC.js} +603 -50
- 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 -1
|
@@ -1518,6 +1518,128 @@ function matchRouteToSteps(journey, route) {
|
|
|
1518
1518
|
return [...exact, ...param];
|
|
1519
1519
|
}
|
|
1520
1520
|
|
|
1521
|
+
// src/lib/fallbackJourney.ts
|
|
1522
|
+
var ANY_PAGE = "*";
|
|
1523
|
+
var FALLBACK_JOURNEY = [
|
|
1524
|
+
{
|
|
1525
|
+
id: "first-run",
|
|
1526
|
+
role: { en: "First look", ar: "\u0627\u0644\u0646\u0638\u0631\u0629 \u0627\u0644\u0623\u0648\u0644\u0649" },
|
|
1527
|
+
steps: [
|
|
1528
|
+
{
|
|
1529
|
+
path: ANY_PAGE,
|
|
1530
|
+
what: { en: "Open the app fresh and let it finish loading", ar: "\u0627\u0641\u062A\u062D \u0627\u0644\u062A\u0637\u0628\u064A\u0642 \u0645\u0646 \u062C\u062F\u064A\u062F \u0648\u0627\u062A\u0631\u0643\u0647 \u064A\u0643\u0645\u0644 \u0627\u0644\u062A\u062D\u0645\u064A\u0644" },
|
|
1531
|
+
expect: {
|
|
1532
|
+
en: "Content appears, nothing flashes or jumps, no error in the corner",
|
|
1533
|
+
ar: "\u064A\u0638\u0647\u0631 \u0627\u0644\u0645\u062D\u062A\u0648\u0649 \u062F\u0648\u0646 \u0648\u0645\u064A\u0636 \u0623\u0648 \u0642\u0641\u0632\u060C \u0648\u0628\u062F\u0648\u0646 \u0631\u0633\u0627\u0644\u0629 \u062E\u0637\u0623"
|
|
1534
|
+
},
|
|
1535
|
+
risk: "red"
|
|
1536
|
+
},
|
|
1537
|
+
{
|
|
1538
|
+
path: ANY_PAGE,
|
|
1539
|
+
what: { en: "Read the first screen as a new user would", ar: "\u0627\u0642\u0631\u0623 \u0627\u0644\u0634\u0627\u0634\u0629 \u0627\u0644\u0623\u0648\u0644\u0649 \u0628\u0639\u064A\u0646 \u0645\u0633\u062A\u062E\u062F\u0645 \u062C\u062F\u064A\u062F" },
|
|
1540
|
+
expect: {
|
|
1541
|
+
en: "It is obvious what this is and what to do next",
|
|
1542
|
+
ar: "\u0648\u0627\u0636\u062D \u0645\u0627 \u0647\u0630\u0627 \u0648\u0645\u0627 \u0627\u0644\u062E\u0637\u0648\u0629 \u0627\u0644\u062A\u0627\u0644\u064A\u0629"
|
|
1543
|
+
},
|
|
1544
|
+
risk: "amber"
|
|
1545
|
+
}
|
|
1546
|
+
]
|
|
1547
|
+
},
|
|
1548
|
+
{
|
|
1549
|
+
id: "moving-around",
|
|
1550
|
+
role: { en: "Moving around", ar: "\u0627\u0644\u062A\u0646\u0642\u0644" },
|
|
1551
|
+
steps: [
|
|
1552
|
+
{
|
|
1553
|
+
path: ANY_PAGE,
|
|
1554
|
+
what: { en: "Visit every item in the main menu", ar: "\u0627\u0641\u062A\u062D \u0643\u0644 \u0639\u0646\u0635\u0631 \u0641\u064A \u0627\u0644\u0642\u0627\u0626\u0645\u0629 \u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629" },
|
|
1555
|
+
expect: { en: "Each one opens the page it names \u2014 no blank screens", ar: "\u0643\u0644 \u0639\u0646\u0635\u0631 \u064A\u0641\u062A\u062D \u0635\u0641\u062D\u062A\u0647 \u2014 \u0628\u062F\u0648\u0646 \u0634\u0627\u0634\u0627\u062A \u0641\u0627\u0631\u063A\u0629" },
|
|
1556
|
+
risk: "red"
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
path: ANY_PAGE,
|
|
1560
|
+
what: { en: "Press the browser Back button after moving", ar: "\u0627\u0636\u063A\u0637 \u0632\u0631 \u0627\u0644\u0631\u062C\u0648\u0639 \u0641\u064A \u0627\u0644\u0645\u062A\u0635\u0641\u062D \u0628\u0639\u062F \u0627\u0644\u062A\u0646\u0642\u0644" },
|
|
1561
|
+
expect: { en: "You land where you were, with your place kept", ar: "\u062A\u0639\u0648\u062F \u0625\u0644\u0649 \u062D\u064A\u062B \u0643\u0646\u062A \u0645\u0639 \u0628\u0642\u0627\u0621 \u0645\u0648\u0636\u0639\u0643" },
|
|
1562
|
+
risk: "amber"
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
path: ANY_PAGE,
|
|
1566
|
+
what: { en: "Reload the page you are on", ar: "\u0623\u0639\u062F \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0635\u0641\u062D\u0629 \u0627\u0644\u062D\u0627\u0644\u064A\u0629" },
|
|
1567
|
+
expect: { en: "The same page comes back, still signed in", ar: "\u062A\u0639\u0648\u062F \u0627\u0644\u0635\u0641\u062D\u0629 \u0646\u0641\u0633\u0647\u0627 \u0648\u064A\u0628\u0642\u0649 \u062A\u0633\u062C\u064A\u0644 \u0627\u0644\u062F\u062E\u0648\u0644" },
|
|
1568
|
+
risk: "red"
|
|
1569
|
+
}
|
|
1570
|
+
]
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
id: "the-main-thing",
|
|
1574
|
+
role: { en: "The main thing", ar: "\u0627\u0644\u0645\u0647\u0645\u0629 \u0627\u0644\u0623\u0633\u0627\u0633\u064A\u0629" },
|
|
1575
|
+
steps: [
|
|
1576
|
+
{
|
|
1577
|
+
path: ANY_PAGE,
|
|
1578
|
+
what: {
|
|
1579
|
+
en: "Do what this app is for, start to finish, without shortcuts",
|
|
1580
|
+
ar: "\u0646\u0641\u0651\u0630 \u0627\u0644\u063A\u0631\u0636 \u0627\u0644\u0623\u0633\u0627\u0633\u064A \u0645\u0646 \u0627\u0644\u062A\u0637\u0628\u064A\u0642 \u0645\u0646 \u0627\u0644\u0628\u062F\u0627\u064A\u0629 \u0644\u0644\u0646\u0647\u0627\u064A\u0629 \u062F\u0648\u0646 \u0627\u062E\u062A\u0635\u0627\u0631\u0627\u062A"
|
|
1581
|
+
},
|
|
1582
|
+
expect: { en: "It completes, and you are told clearly that it did", ar: "\u062A\u0643\u062A\u0645\u0644 \u0627\u0644\u0645\u0647\u0645\u0629 \u0648\u064A\u0635\u0644\u0643 \u062A\u0623\u0643\u064A\u062F \u0648\u0627\u0636\u062D" },
|
|
1583
|
+
risk: "red"
|
|
1584
|
+
},
|
|
1585
|
+
{
|
|
1586
|
+
path: ANY_PAGE,
|
|
1587
|
+
what: { en: "Do it a second time with different choices", ar: "\u0643\u0631\u0631\u0647\u0627 \u0645\u0631\u0629 \u062B\u0627\u0646\u064A\u0629 \u0628\u062E\u064A\u0627\u0631\u0627\u062A \u0645\u062E\u062A\u0644\u0641\u0629" },
|
|
1588
|
+
expect: { en: "Same result, nothing left over from the first run", ar: "\u0627\u0644\u0646\u062A\u064A\u062C\u0629 \u0646\u0641\u0633\u0647\u0627\u060C \u062F\u0648\u0646 \u0628\u0642\u0627\u064A\u0627 \u0645\u0646 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0627\u0644\u0623\u0648\u0644\u0649" },
|
|
1589
|
+
risk: "amber"
|
|
1590
|
+
}
|
|
1591
|
+
]
|
|
1592
|
+
},
|
|
1593
|
+
{
|
|
1594
|
+
id: "when-it-goes-wrong",
|
|
1595
|
+
role: { en: "When it goes wrong", ar: "\u0639\u0646\u062F \u0627\u0644\u062E\u0637\u0623" },
|
|
1596
|
+
steps: [
|
|
1597
|
+
{
|
|
1598
|
+
path: ANY_PAGE,
|
|
1599
|
+
what: { en: "Submit a form empty, then with obviously wrong values", ar: "\u0623\u0631\u0633\u0644 \u0627\u0644\u0646\u0645\u0648\u0630\u062C \u0641\u0627\u0631\u063A\u064B\u0627 \u062B\u0645 \u0628\u0642\u064A\u0645 \u062E\u0627\u0637\u0626\u0629 \u0628\u0648\u0636\u0648\u062D" },
|
|
1600
|
+
expect: {
|
|
1601
|
+
en: "It refuses politely and says which field and why \u2014 it does not crash or save",
|
|
1602
|
+
ar: "\u064A\u0631\u0641\u0636 \u0628\u0644\u0637\u0641 \u0648\u064A\u0648\u0636\u062D \u0627\u0644\u062D\u0642\u0644 \u0648\u0627\u0644\u0633\u0628\u0628 \u2014 \u062F\u0648\u0646 \u062A\u0639\u0637\u0644 \u0623\u0648 \u062D\u0641\u0638"
|
|
1603
|
+
},
|
|
1604
|
+
risk: "red"
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
path: ANY_PAGE,
|
|
1608
|
+
what: { en: "Press the main button twice, quickly", ar: "\u0627\u0636\u063A\u0637 \u0627\u0644\u0632\u0631 \u0627\u0644\u0631\u0626\u064A\u0633\u064A \u0645\u0631\u062A\u064A\u0646 \u0628\u0633\u0631\u0639\u0629" },
|
|
1609
|
+
expect: { en: "One result, not two", ar: "\u0646\u062A\u064A\u062C\u0629 \u0648\u0627\u062D\u062F\u0629 \u0644\u0627 \u0627\u062B\u0646\u062A\u0627\u0646" },
|
|
1610
|
+
risk: "amber"
|
|
1611
|
+
},
|
|
1612
|
+
{
|
|
1613
|
+
path: ANY_PAGE,
|
|
1614
|
+
what: { en: "Open a page you should not be allowed to see", ar: "\u0627\u0641\u062A\u062D \u0635\u0641\u062D\u0629 \u0644\u0627 \u064A\u064F\u0641\u062A\u0631\u0636 \u0623\u0646 \u062A\u0635\u0644 \u0625\u0644\u064A\u0647\u0627" },
|
|
1615
|
+
expect: { en: "You are turned away, not shown someone else\u2019s data", ar: "\u064A\u062A\u0645 \u0645\u0646\u0639\u0643\u060C \u0648\u0644\u0627 \u062A\u0638\u0647\u0631 \u0628\u064A\u0627\u0646\u0627\u062A \u0634\u062E\u0635 \u0622\u062E\u0631" },
|
|
1616
|
+
risk: "red"
|
|
1617
|
+
}
|
|
1618
|
+
]
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
id: "on-a-phone",
|
|
1622
|
+
role: { en: "On a phone", ar: "\u0639\u0644\u0649 \u0627\u0644\u0647\u0627\u062A\u0641" },
|
|
1623
|
+
steps: [
|
|
1624
|
+
{
|
|
1625
|
+
path: ANY_PAGE,
|
|
1626
|
+
what: { en: "Make the window narrow, or open it on a phone", ar: "\u0635\u063A\u0651\u0631 \u0627\u0644\u0646\u0627\u0641\u0630\u0629 \u0623\u0648 \u0627\u0641\u062A\u062D\u0647 \u0639\u0644\u0649 \u0627\u0644\u0647\u0627\u062A\u0641" },
|
|
1627
|
+
expect: {
|
|
1628
|
+
en: "Nothing is cut off, nothing overlaps, no sideways scrolling",
|
|
1629
|
+
ar: "\u0644\u0627 \u0634\u064A\u0621 \u0645\u0642\u0635\u0648\u0635 \u0623\u0648 \u0645\u062A\u062F\u0627\u062E\u0644\u060C \u0648\u0628\u062F\u0648\u0646 \u062A\u0645\u0631\u064A\u0631 \u062C\u0627\u0646\u0628\u064A"
|
|
1630
|
+
},
|
|
1631
|
+
risk: "amber"
|
|
1632
|
+
},
|
|
1633
|
+
{
|
|
1634
|
+
path: ANY_PAGE,
|
|
1635
|
+
what: { en: "Tap the small controls with a thumb", ar: "\u0627\u0636\u063A\u0637 \u0627\u0644\u0639\u0646\u0627\u0635\u0631 \u0627\u0644\u0635\u063A\u064A\u0631\u0629 \u0628\u0625\u0628\u0647\u0627\u0645\u0643" },
|
|
1636
|
+
expect: { en: "They are big enough to hit first time", ar: "\u0643\u0628\u064A\u0631\u0629 \u0628\u0645\u0627 \u064A\u0643\u0641\u064A \u0644\u0644\u0636\u063A\u0637 \u0645\u0646 \u0623\u0648\u0644 \u0645\u0631\u0629" },
|
|
1637
|
+
risk: "green"
|
|
1638
|
+
}
|
|
1639
|
+
]
|
|
1640
|
+
}
|
|
1641
|
+
];
|
|
1642
|
+
|
|
1521
1643
|
// src/lib/storage.ts
|
|
1522
1644
|
function isStorageAvailable() {
|
|
1523
1645
|
if (typeof window === "undefined") return false;
|
|
@@ -1620,6 +1742,7 @@ var STR = {
|
|
|
1620
1742
|
loc_hide: "Hide location",
|
|
1621
1743
|
loc_locate: "Locate on page",
|
|
1622
1744
|
journey_title: "Testing journey",
|
|
1745
|
+
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.",
|
|
1623
1746
|
export_name_title: "Name your export",
|
|
1624
1747
|
export_name_placeholder: "file name",
|
|
1625
1748
|
tap_element: "Tap an element",
|
|
@@ -1683,6 +1806,12 @@ var STR = {
|
|
|
1683
1806
|
sync_lost_permission: "Folder access expired \u2014 click Reconnect to keep saving",
|
|
1684
1807
|
sync_write_failed: "Couldn't write to the folder \u2014 notes are still saved in this browser",
|
|
1685
1808
|
sync_unsupported: "Folder saving needs Chrome, Edge or Brave on desktop. Use Export here.",
|
|
1809
|
+
sync_zip_mode: "This browser cannot write into a folder directly, so Qapture builds the folder for you: name the project and campaign, and you get a zip that unzips into exactly that structure. It refreshes itself every few points.",
|
|
1810
|
+
sync_zip_start: "Start saving",
|
|
1811
|
+
sync_zip_save_now: "Save folder now",
|
|
1812
|
+
sync_zip_saved: "Saved {name} to your downloads",
|
|
1813
|
+
sync_zip_failed: "Couldn't build the folder \u2014 your notes are still safe in this browser",
|
|
1814
|
+
sync_zip_where: "Unzip into your QA folder:",
|
|
1686
1815
|
sync_writing_to: "Writing to",
|
|
1687
1816
|
storage_title: "Storage",
|
|
1688
1817
|
storage_used: "{used} of {quota} used",
|
|
@@ -1807,6 +1936,7 @@ var STR = {
|
|
|
1807
1936
|
loc_hide: "\u0625\u062E\u0641\u0627\u0621 \u0627\u0644\u0645\u0648\u0642\u0639",
|
|
1808
1937
|
loc_locate: "\u0625\u0638\u0647\u0627\u0631 \u0639\u0644\u0649 \u0627\u0644\u0635\u0641\u062D\u0629",
|
|
1809
1938
|
journey_title: "\u0631\u062D\u0644\u0629 \u0627\u0644\u0627\u062E\u062A\u0628\u0627\u0631",
|
|
1939
|
+
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.",
|
|
1810
1940
|
export_name_title: "\u0633\u0645\u0650\u0651 \u0645\u0644\u0641 \u0627\u0644\u062A\u0635\u062F\u064A\u0631",
|
|
1811
1941
|
export_name_placeholder: "\u0627\u0633\u0645 \u0627\u0644\u0645\u0644\u0641",
|
|
1812
1942
|
tap_element: "\u0627\u0646\u0642\u0631 \u0639\u0644\u0649 \u0639\u0646\u0635\u0631",
|
|
@@ -1870,6 +2000,12 @@ var STR = {
|
|
|
1870
2000
|
sync_lost_permission: "\u0627\u0646\u062A\u0647\u062A \u0635\u0644\u0627\u062D\u064A\u0629 \u0627\u0644\u0648\u0635\u0648\u0644 \u0644\u0644\u0645\u062C\u0644\u062F \u2014 \u0627\u0636\u063A\u0637 \u0625\u0639\u0627\u062F\u0629 \u0627\u0644\u0631\u0628\u0637 \u0644\u0644\u0645\u062A\u0627\u0628\u0639\u0629",
|
|
1871
2001
|
sync_write_failed: "\u062A\u0639\u0630\u0651\u0631\u062A \u0627\u0644\u0643\u062A\u0627\u0628\u0629 \u0641\u064A \u0627\u0644\u0645\u062C\u0644\u062F \u2014 \u0627\u0644\u0645\u0644\u0627\u062D\u0638\u0627\u062A \u0645\u062D\u0641\u0648\u0638\u0629 \u0641\u064A \u0627\u0644\u0645\u062A\u0635\u0641\u062D",
|
|
1872
2002
|
sync_unsupported: "\u0627\u0644\u062D\u0641\u0638 \u0641\u064A \u0645\u062C\u0644\u062F \u064A\u062A\u0637\u0644\u0628 Chrome \u0623\u0648 Edge \u0623\u0648 Brave \u0639\u0644\u0649 \u0633\u0637\u062D \u0627\u0644\u0645\u0643\u062A\u0628. \u0627\u0633\u062A\u062E\u062F\u0645 \u0627\u0644\u062A\u0635\u062F\u064A\u0631 \u0647\u0646\u0627.",
|
|
2003
|
+
sync_zip_mode: "\u0647\u0630\u0627 \u0627\u0644\u0645\u062A\u0635\u0641\u062D \u0644\u0627 \u064A\u0633\u062A\u0637\u064A\u0639 \u0627\u0644\u0643\u062A\u0627\u0628\u0629 \u062F\u0627\u062E\u0644 \u0645\u062C\u0644\u062F \u0645\u0628\u0627\u0634\u0631\u0629\u060C \u0644\u0630\u0644\u0643 \u064A\u0628\u0646\u064A Qapture \u0627\u0644\u0645\u062C\u0644\u062F \u0646\u064A\u0627\u0628\u0629\u064B \u0639\u0646\u0643: \u0627\u0643\u062A\u0628 \u0627\u0633\u0645 \u0627\u0644\u0645\u0634\u0631\u0648\u0639 \u0648\u0627\u0644\u062D\u0645\u0644\u0629\u060C \u0648\u0633\u062A\u062D\u0635\u0644 \u0639\u0644\u0649 \u0645\u0644\u0641 \u0645\u0636\u063A\u0648\u0637 \u064A\u064F\u0641\u064E\u0643 \u0625\u0644\u0649 \u0627\u0644\u0628\u0646\u064A\u0629 \u0646\u0641\u0633\u0647\u0627 \u062A\u0645\u0627\u0645\u064B\u0627. \u0648\u064A\u062A\u062D\u062F\u0651\u062B \u062A\u0644\u0642\u0627\u0626\u064A\u064B\u0627 \u0643\u0644 \u0628\u0636\u0639 \u0646\u0642\u0627\u0637.",
|
|
2004
|
+
sync_zip_start: "\u0627\u0628\u062F\u0623 \u0627\u0644\u062D\u0641\u0638",
|
|
2005
|
+
sync_zip_save_now: "\u0627\u062D\u0641\u0638 \u0627\u0644\u0645\u062C\u0644\u062F \u0627\u0644\u0622\u0646",
|
|
2006
|
+
sync_zip_saved: "\u062A\u0645 \u062D\u0641\u0638 {name} \u0641\u064A \u0627\u0644\u062A\u0646\u0632\u064A\u0644\u0627\u062A",
|
|
2007
|
+
sync_zip_failed: "\u062A\u0639\u0630\u0651\u0631 \u0628\u0646\u0627\u0621 \u0627\u0644\u0645\u062C\u0644\u062F \u2014 \u0645\u0644\u0627\u062D\u0638\u0627\u062A\u0643 \u0645\u0627 \u0632\u0627\u0644\u062A \u0645\u062D\u0641\u0648\u0638\u0629 \u0641\u064A \u0647\u0630\u0627 \u0627\u0644\u0645\u062A\u0635\u0641\u062D",
|
|
2008
|
+
sync_zip_where: "\u0641\u064F\u0643\u0651 \u0627\u0644\u0636\u063A\u0637 \u062F\u0627\u062E\u0644 \u0645\u062C\u0644\u062F QA \u0644\u062F\u064A\u0643:",
|
|
1873
2009
|
sync_writing_to: "\u0627\u0644\u0643\u062A\u0627\u0628\u0629 \u0625\u0644\u0649",
|
|
1874
2010
|
storage_title: "\u0627\u0644\u062A\u062E\u0632\u064A\u0646",
|
|
1875
2011
|
storage_used: "{used} \u0645\u0646 {quota} \u0645\u0633\u062A\u062E\u062F\u064E\u0645\u0629",
|
|
@@ -2256,12 +2392,153 @@ async function grabExactRegion(rect) {
|
|
|
2256
2392
|
}
|
|
2257
2393
|
}
|
|
2258
2394
|
|
|
2395
|
+
// src/lib/cssColors.ts
|
|
2396
|
+
var UNSUPPORTED_FN = /\b(?:oklch|oklab|lch|lab|color-mix|color)\(/i;
|
|
2397
|
+
function unsupportedFnScanner() {
|
|
2398
|
+
return /\b(?:oklch|oklab|lch|lab|color-mix|color)\(/gi;
|
|
2399
|
+
}
|
|
2400
|
+
var COLOR_PROPS = [
|
|
2401
|
+
"color",
|
|
2402
|
+
"backgroundColor",
|
|
2403
|
+
"borderTopColor",
|
|
2404
|
+
"borderRightColor",
|
|
2405
|
+
"borderBottomColor",
|
|
2406
|
+
"borderLeftColor",
|
|
2407
|
+
"outlineColor",
|
|
2408
|
+
"textDecorationColor",
|
|
2409
|
+
"columnRuleColor",
|
|
2410
|
+
"caretColor",
|
|
2411
|
+
"fill",
|
|
2412
|
+
"stroke"
|
|
2413
|
+
];
|
|
2414
|
+
var COMPOUND_PROPS = ["backgroundImage", "boxShadow", "textShadow"];
|
|
2415
|
+
var probeCtx;
|
|
2416
|
+
function getProbe() {
|
|
2417
|
+
if (probeCtx !== void 0) return probeCtx;
|
|
2418
|
+
try {
|
|
2419
|
+
const canvas = document.createElement("canvas");
|
|
2420
|
+
canvas.width = canvas.height = 1;
|
|
2421
|
+
probeCtx = canvas.getContext("2d", { willReadFrequently: true });
|
|
2422
|
+
} catch {
|
|
2423
|
+
probeCtx = null;
|
|
2424
|
+
}
|
|
2425
|
+
return probeCtx;
|
|
2426
|
+
}
|
|
2427
|
+
var conversionCache = /* @__PURE__ */ new Map();
|
|
2428
|
+
function toRenderableColor(value) {
|
|
2429
|
+
const ctx = getProbe();
|
|
2430
|
+
if (!ctx || !value) return null;
|
|
2431
|
+
const cached = conversionCache.get(value);
|
|
2432
|
+
if (cached !== void 0) return cached;
|
|
2433
|
+
const result = convertUncached(ctx, value);
|
|
2434
|
+
conversionCache.set(value, result);
|
|
2435
|
+
return result;
|
|
2436
|
+
}
|
|
2437
|
+
function convertUncached(ctx, value) {
|
|
2438
|
+
try {
|
|
2439
|
+
ctx.fillStyle = "#000000";
|
|
2440
|
+
ctx.fillStyle = value;
|
|
2441
|
+
const fromBlack = String(ctx.fillStyle);
|
|
2442
|
+
ctx.fillStyle = "#ffffff";
|
|
2443
|
+
ctx.fillStyle = value;
|
|
2444
|
+
if (fromBlack !== String(ctx.fillStyle)) return null;
|
|
2445
|
+
if (!UNSUPPORTED_FN.test(fromBlack)) return fromBlack;
|
|
2446
|
+
ctx.clearRect(0, 0, 1, 1);
|
|
2447
|
+
ctx.fillStyle = value;
|
|
2448
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
2449
|
+
const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
|
|
2450
|
+
return a === 255 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${(a / 255).toFixed(3)})`;
|
|
2451
|
+
} catch {
|
|
2452
|
+
return null;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
function neutralizeColorFunctions(value) {
|
|
2456
|
+
if (!value || !UNSUPPORTED_FN.test(value)) return value;
|
|
2457
|
+
const scanner = unsupportedFnScanner();
|
|
2458
|
+
let out = "";
|
|
2459
|
+
let last = 0;
|
|
2460
|
+
let match;
|
|
2461
|
+
while ((match = scanner.exec(value)) !== null) {
|
|
2462
|
+
const start = match.index;
|
|
2463
|
+
if (start < last) continue;
|
|
2464
|
+
let depth = 0;
|
|
2465
|
+
let i = start + match[0].length - 1;
|
|
2466
|
+
for (; i < value.length; i++) {
|
|
2467
|
+
if (value[i] === "(") depth++;
|
|
2468
|
+
else if (value[i] === ")") {
|
|
2469
|
+
depth--;
|
|
2470
|
+
if (depth === 0) break;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
if (i >= value.length) break;
|
|
2474
|
+
const converted = toRenderableColor(value.slice(start, i + 1));
|
|
2475
|
+
out += value.slice(last, start) + (converted ?? "rgba(0, 0, 0, 0)");
|
|
2476
|
+
last = i + 1;
|
|
2477
|
+
scanner.lastIndex = last;
|
|
2478
|
+
}
|
|
2479
|
+
return out + value.slice(last);
|
|
2480
|
+
}
|
|
2481
|
+
function hasUnsupportedColor(value) {
|
|
2482
|
+
return !!value && UNSUPPORTED_FN.test(value);
|
|
2483
|
+
}
|
|
2484
|
+
function safeBackgroundColor(value, fallback = "#ffffff") {
|
|
2485
|
+
if (!hasUnsupportedColor(value)) return value;
|
|
2486
|
+
return toRenderableColor(value) ?? fallback;
|
|
2487
|
+
}
|
|
2488
|
+
function neutralizeDocumentColors(doc, aggressive = false) {
|
|
2489
|
+
const view = doc.defaultView;
|
|
2490
|
+
if (!view || typeof view.getComputedStyle !== "function") return 0;
|
|
2491
|
+
let touched = 0;
|
|
2492
|
+
const all = doc.querySelectorAll("*");
|
|
2493
|
+
const elements = [doc.documentElement, ...Array.from(all)];
|
|
2494
|
+
for (const el of elements) {
|
|
2495
|
+
if (!el || !el.style) continue;
|
|
2496
|
+
let changed = false;
|
|
2497
|
+
let computed;
|
|
2498
|
+
try {
|
|
2499
|
+
computed = view.getComputedStyle(el);
|
|
2500
|
+
} catch {
|
|
2501
|
+
continue;
|
|
2502
|
+
}
|
|
2503
|
+
for (const prop of COLOR_PROPS) {
|
|
2504
|
+
const value = computed[prop] ?? computed[prop];
|
|
2505
|
+
if (!hasUnsupportedColor(value)) continue;
|
|
2506
|
+
const converted = toRenderableColor(value);
|
|
2507
|
+
if (converted) {
|
|
2508
|
+
el.style[prop] = converted;
|
|
2509
|
+
changed = true;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
for (const prop of COMPOUND_PROPS) {
|
|
2513
|
+
const value = computed[prop];
|
|
2514
|
+
if (!value || value === "none") continue;
|
|
2515
|
+
if (aggressive && prop !== "backgroundImage") {
|
|
2516
|
+
el.style[prop] = "none";
|
|
2517
|
+
changed = true;
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
if (!hasUnsupportedColor(value)) continue;
|
|
2521
|
+
if (aggressive) {
|
|
2522
|
+
el.style[prop] = "none";
|
|
2523
|
+
changed = true;
|
|
2524
|
+
continue;
|
|
2525
|
+
}
|
|
2526
|
+
el.style[prop] = neutralizeColorFunctions(value);
|
|
2527
|
+
changed = true;
|
|
2528
|
+
}
|
|
2529
|
+
if (changed) touched++;
|
|
2530
|
+
}
|
|
2531
|
+
return touched;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2259
2534
|
// src/lib/capture.ts
|
|
2260
2535
|
var HTML2CANVAS_TIMEOUT_MS = 1e4;
|
|
2261
2536
|
var FALLBACK_PAGE_BACKGROUND = "#ffffff";
|
|
2262
2537
|
var MAX_SHOT_EDGE = 1800;
|
|
2263
2538
|
var WEBP_QUALITY = 0.92;
|
|
2264
2539
|
var STUCK_ATTR = "data-qa-stuck";
|
|
2540
|
+
var MAX_CAPTURE_EDGE = 4e3;
|
|
2541
|
+
var MAX_RENDER_VIEWPORTS = 4;
|
|
2265
2542
|
function withTimeout(promise, ms) {
|
|
2266
2543
|
let timer;
|
|
2267
2544
|
return Promise.race([
|
|
@@ -2427,37 +2704,86 @@ function cropCanvas(full, rect, scale) {
|
|
|
2427
2704
|
ctx.drawImage(full, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
2428
2705
|
return out;
|
|
2429
2706
|
}
|
|
2430
|
-
|
|
2707
|
+
function viewportSize() {
|
|
2708
|
+
return {
|
|
2709
|
+
vw: document.documentElement.clientWidth || window.innerWidth,
|
|
2710
|
+
vh: document.documentElement.clientHeight || window.innerHeight
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
function intersectViewport(rect) {
|
|
2714
|
+
const { vw, vh } = viewportSize();
|
|
2715
|
+
const left = Math.max(0, rect.left);
|
|
2716
|
+
const top = Math.max(0, rect.top);
|
|
2717
|
+
const right = Math.min(vw, rect.left + rect.width);
|
|
2718
|
+
const bottom = Math.min(vh, rect.top + rect.height);
|
|
2719
|
+
if (right - left < 2 || bottom - top < 2) return null;
|
|
2720
|
+
return { left, top, width: right - left, height: bottom - top };
|
|
2721
|
+
}
|
|
2722
|
+
function planRender(rect, sx, sy, vw, vh) {
|
|
2723
|
+
let left = rect.left + sx;
|
|
2724
|
+
let top = rect.top + sy;
|
|
2725
|
+
let width = rect.width;
|
|
2726
|
+
let height = rect.height;
|
|
2727
|
+
if (width > MAX_CAPTURE_EDGE) {
|
|
2728
|
+
left = Math.max(left, Math.min(sx, left + width - MAX_CAPTURE_EDGE));
|
|
2729
|
+
width = MAX_CAPTURE_EDGE;
|
|
2730
|
+
}
|
|
2731
|
+
if (height > MAX_CAPTURE_EDGE) {
|
|
2732
|
+
top = Math.max(top, Math.min(sy, top + height - MAX_CAPTURE_EDGE));
|
|
2733
|
+
height = MAX_CAPTURE_EDGE;
|
|
2734
|
+
}
|
|
2735
|
+
const x = Math.floor(Math.min(sx, left));
|
|
2736
|
+
const y = Math.floor(Math.min(sy, top));
|
|
2737
|
+
const right = Math.ceil(Math.max(sx + vw, left + width));
|
|
2738
|
+
const bottom = Math.ceil(Math.max(sy + vh, top + height));
|
|
2739
|
+
return {
|
|
2740
|
+
render: { x, y, width: right - x, height: bottom - y },
|
|
2741
|
+
crop: { left: left - x, top: top - y, width, height }
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
async function captureViaDom(rect, sx, sy, aggressiveColors = false) {
|
|
2431
2745
|
const { default: html2canvas } = await import('html2canvas');
|
|
2432
|
-
const
|
|
2433
|
-
const
|
|
2434
|
-
|
|
2746
|
+
const { vw, vh } = viewportSize();
|
|
2747
|
+
const plan = planRender(rect, sx, sy, vw, vh);
|
|
2748
|
+
let scale = Math.min(window.devicePixelRatio || 1, 2);
|
|
2749
|
+
const budget = vw * vh * MAX_RENDER_VIEWPORTS;
|
|
2750
|
+
const area = plan.render.width * plan.render.height;
|
|
2751
|
+
if (area > budget) scale *= Math.sqrt(budget / area);
|
|
2435
2752
|
const marked = markStuckElements();
|
|
2436
2753
|
try {
|
|
2437
2754
|
const full = await withTimeout(
|
|
2438
2755
|
html2canvas(document.body, {
|
|
2439
|
-
//
|
|
2440
|
-
x:
|
|
2441
|
-
y:
|
|
2442
|
-
width:
|
|
2443
|
-
height:
|
|
2756
|
+
// The box to render, in document coordinates.
|
|
2757
|
+
x: plan.render.x,
|
|
2758
|
+
y: plan.render.y,
|
|
2759
|
+
width: plan.render.width,
|
|
2760
|
+
height: plan.render.height,
|
|
2444
2761
|
scale,
|
|
2445
2762
|
useCORS: true,
|
|
2446
2763
|
allowTaint: true,
|
|
2447
2764
|
// The page's own background, never null — see resolvePageBackground().
|
|
2448
|
-
|
|
2765
|
+
// Passed straight to html2canvas's parser, so it gets the same
|
|
2766
|
+
// CSS Color 4 treatment the document walk applies.
|
|
2767
|
+
backgroundColor: safeBackgroundColor(resolvePageBackground(), FALLBACK_PAGE_BACKGROUND),
|
|
2449
2768
|
logging: false,
|
|
2769
|
+
// The clone's own scroll + viewport, which is what `position: fixed`
|
|
2770
|
+
// resolves against. It stays the LIVE viewport even when the render
|
|
2771
|
+
// box is bigger, so fixed chrome is drawn where the tester sees it
|
|
2772
|
+
// rather than smeared over the whole render.
|
|
2450
2773
|
scrollX: sx,
|
|
2451
2774
|
scrollY: sy,
|
|
2452
2775
|
windowWidth: vw,
|
|
2453
2776
|
windowHeight: vh,
|
|
2454
2777
|
ignoreElements: (el) => el.nodeType === 1 && typeof el.hasAttribute === "function" && el.hasAttribute("data-qa-overlay"),
|
|
2455
|
-
onclone: (doc) =>
|
|
2778
|
+
onclone: (doc) => {
|
|
2779
|
+
neutralizeDocumentColors(doc, aggressiveColors);
|
|
2780
|
+
pinStuckClones(doc);
|
|
2781
|
+
}
|
|
2456
2782
|
}),
|
|
2457
2783
|
HTML2CANVAS_TIMEOUT_MS
|
|
2458
2784
|
);
|
|
2459
2785
|
if (!full) return null;
|
|
2460
|
-
return cropCanvas(full,
|
|
2786
|
+
return cropCanvas(full, plan.crop, scale);
|
|
2461
2787
|
} finally {
|
|
2462
2788
|
unmarkStuckElements(marked);
|
|
2463
2789
|
}
|
|
@@ -2470,8 +2796,10 @@ async function captureRegion(rect, scroll, prefer = "auto") {
|
|
|
2470
2796
|
const sx = scroll?.x ?? window.scrollX;
|
|
2471
2797
|
const sy = scroll?.y ?? window.scrollY;
|
|
2472
2798
|
if (prefer !== "dom" && getExactCaptureStatus() === "live") {
|
|
2799
|
+
const visible = intersectViewport(rect);
|
|
2800
|
+
if (!visible) return { status: "empty" };
|
|
2473
2801
|
try {
|
|
2474
|
-
const canvas = await withOverlayHidden(() => grabExactRegion(
|
|
2802
|
+
const canvas = await withOverlayHidden(() => grabExactRegion(visible));
|
|
2475
2803
|
if (canvas) {
|
|
2476
2804
|
const blob = await encodeShot(canvas);
|
|
2477
2805
|
if (blob) return { status: "ok", blob, engine: "exact" };
|
|
@@ -2487,8 +2815,16 @@ async function captureRegion(rect, scroll, prefer = "auto") {
|
|
|
2487
2815
|
const blob = await encodeShot(canvas);
|
|
2488
2816
|
return blob ? { status: "ok", blob, engine: "dom" } : { status: "failed" };
|
|
2489
2817
|
} catch (err) {
|
|
2490
|
-
console.warn("[QA] region capture failed:", err);
|
|
2491
|
-
|
|
2818
|
+
console.warn("[QA] region capture failed, retrying without gradients/shadows:", err);
|
|
2819
|
+
try {
|
|
2820
|
+
const canvas = await captureViaDom(rect, sx, sy, true);
|
|
2821
|
+
if (!canvas) return { status: "failed" };
|
|
2822
|
+
const blob = await encodeShot(canvas);
|
|
2823
|
+
return blob ? { status: "ok", blob, engine: "dom" } : { status: "failed" };
|
|
2824
|
+
} catch (retryErr) {
|
|
2825
|
+
console.warn("[QA] region capture failed after retry:", retryErr);
|
|
2826
|
+
return { status: "failed" };
|
|
2827
|
+
}
|
|
2492
2828
|
}
|
|
2493
2829
|
}
|
|
2494
2830
|
|
|
@@ -2911,6 +3247,7 @@ async function shareZipFile(blob, filename, title) {
|
|
|
2911
3247
|
// src/lib/fsSync.ts
|
|
2912
3248
|
var MAX_SLUG_LENGTH = 48;
|
|
2913
3249
|
var FS_ROOT_META_KEY = "fsSyncRoot";
|
|
3250
|
+
var FS_CAMPAIGN_META_KEY = "fsSyncCampaign";
|
|
2914
3251
|
var root = null;
|
|
2915
3252
|
var campaignDir = null;
|
|
2916
3253
|
var notesDir = null;
|
|
@@ -2919,6 +3256,7 @@ var campaign = null;
|
|
|
2919
3256
|
var noteIndex = {};
|
|
2920
3257
|
var state = "unsupported";
|
|
2921
3258
|
var lastError = "";
|
|
3259
|
+
var metaStore = null;
|
|
2922
3260
|
var bulkWriting = false;
|
|
2923
3261
|
var listeners = /* @__PURE__ */ new Set();
|
|
2924
3262
|
function setState(next, error = "") {
|
|
@@ -2941,8 +3279,20 @@ function onFsSyncChange(fn) {
|
|
|
2941
3279
|
function isFsSyncSupported() {
|
|
2942
3280
|
return typeof window !== "undefined" && typeof window.showDirectoryPicker === "function";
|
|
2943
3281
|
}
|
|
3282
|
+
function isFsSyncAvailable() {
|
|
3283
|
+
return typeof window !== "undefined";
|
|
3284
|
+
}
|
|
3285
|
+
function getFsSyncEngine() {
|
|
3286
|
+
if (isFsSyncSupported() || typeof document === "undefined") return "disk";
|
|
3287
|
+
return "download";
|
|
3288
|
+
}
|
|
2944
3289
|
function getFsSyncState() {
|
|
2945
|
-
if (!
|
|
3290
|
+
if (!isFsSyncAvailable()) return "unsupported";
|
|
3291
|
+
if (!isFsSyncSupported()) {
|
|
3292
|
+
if (state === "syncing") return "syncing";
|
|
3293
|
+
if (state === "error") return "error";
|
|
3294
|
+
return "connected";
|
|
3295
|
+
}
|
|
2946
3296
|
return state === "unsupported" ? "off" : state;
|
|
2947
3297
|
}
|
|
2948
3298
|
function getFsSyncError() {
|
|
@@ -2952,9 +3302,18 @@ function getFsSyncCampaign() {
|
|
|
2952
3302
|
return campaign;
|
|
2953
3303
|
}
|
|
2954
3304
|
function getFsSyncPath() {
|
|
3305
|
+
if (getFsSyncEngine() === "download") return campaign ? campaignRelativePath() : "";
|
|
2955
3306
|
if (!root) return "";
|
|
2956
3307
|
if (!campaign) return root.name;
|
|
2957
|
-
return `${root.name}/${
|
|
3308
|
+
return `${root.name}/${campaignRelativePath()}`;
|
|
3309
|
+
}
|
|
3310
|
+
function campaignRelativePath() {
|
|
3311
|
+
if (!campaign) return "";
|
|
3312
|
+
return `${safeSegment(campaign.project)}/${safeSegment(campaign.campaign)}`;
|
|
3313
|
+
}
|
|
3314
|
+
function campaignZipFileName() {
|
|
3315
|
+
if (!campaign) return "qa-campaign.zip";
|
|
3316
|
+
return `${safeSegment(campaign.project)} - ${safeSegment(campaign.campaign)}.zip`;
|
|
2958
3317
|
}
|
|
2959
3318
|
function safeSegment(input, fallback = "untitled") {
|
|
2960
3319
|
let s = (input ?? "").normalize("NFC").replace(/[\\/:*?"<>|\u0000-\u001F]/g, "-");
|
|
@@ -2996,10 +3355,25 @@ async function permissionFor(handle, request) {
|
|
|
2996
3355
|
return "granted";
|
|
2997
3356
|
}
|
|
2998
3357
|
async function restoreFsSync(store) {
|
|
2999
|
-
|
|
3358
|
+
metaStore = store;
|
|
3359
|
+
if (!isFsSyncAvailable()) {
|
|
3000
3360
|
setState("unsupported");
|
|
3001
3361
|
return "unsupported";
|
|
3002
3362
|
}
|
|
3363
|
+
if (getFsSyncEngine() === "download") {
|
|
3364
|
+
try {
|
|
3365
|
+
const saved = await store.getMeta(FS_CAMPAIGN_META_KEY);
|
|
3366
|
+
if (saved?.campaign?.project && saved.active !== false) {
|
|
3367
|
+
campaign = saved.campaign;
|
|
3368
|
+
noteIndex = saved.notes ?? {};
|
|
3369
|
+
setState("syncing");
|
|
3370
|
+
return "syncing";
|
|
3371
|
+
}
|
|
3372
|
+
} catch {
|
|
3373
|
+
}
|
|
3374
|
+
setState("connected");
|
|
3375
|
+
return "connected";
|
|
3376
|
+
}
|
|
3003
3377
|
try {
|
|
3004
3378
|
const saved = await store.getMeta(FS_ROOT_META_KEY);
|
|
3005
3379
|
if (!saved || typeof saved.getDirectoryHandle !== "function") {
|
|
@@ -3016,7 +3390,11 @@ async function restoreFsSync(store) {
|
|
|
3016
3390
|
}
|
|
3017
3391
|
}
|
|
3018
3392
|
async function chooseFsSyncRoot(store) {
|
|
3019
|
-
|
|
3393
|
+
metaStore = store;
|
|
3394
|
+
if (!isFsSyncSupported()) {
|
|
3395
|
+
setState("connected");
|
|
3396
|
+
return true;
|
|
3397
|
+
}
|
|
3020
3398
|
const picker = window.showDirectoryPicker;
|
|
3021
3399
|
try {
|
|
3022
3400
|
const handle = await picker({ id: "qapture-qa", mode: "readwrite", startIn: "documents" });
|
|
@@ -3064,6 +3442,30 @@ function describeError(err) {
|
|
|
3064
3442
|
return e?.message || "write-failed";
|
|
3065
3443
|
}
|
|
3066
3444
|
async function openFsCampaign(input) {
|
|
3445
|
+
if (getFsSyncEngine() === "download") {
|
|
3446
|
+
const sameAsBefore = campaign?.project === input.project && campaign?.campaign === input.campaign;
|
|
3447
|
+
let startedAt = sameAsBefore && campaign ? campaign.startedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
3448
|
+
if (!sameAsBefore) {
|
|
3449
|
+
noteIndex = {};
|
|
3450
|
+
try {
|
|
3451
|
+
const saved = await metaStore?.getMeta(FS_CAMPAIGN_META_KEY);
|
|
3452
|
+
if (saved?.campaign?.project === input.project && saved.campaign.campaign === input.campaign) {
|
|
3453
|
+
noteIndex = saved.notes ?? {};
|
|
3454
|
+
startedAt = saved.campaign.startedAt || startedAt;
|
|
3455
|
+
}
|
|
3456
|
+
} catch {
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
campaign = {
|
|
3460
|
+
project: input.project,
|
|
3461
|
+
campaign: input.campaign,
|
|
3462
|
+
tester: input.tester,
|
|
3463
|
+
startedAt
|
|
3464
|
+
};
|
|
3465
|
+
await persistDownloadCampaign();
|
|
3466
|
+
setState("syncing");
|
|
3467
|
+
return true;
|
|
3468
|
+
}
|
|
3067
3469
|
if (!root) return false;
|
|
3068
3470
|
try {
|
|
3069
3471
|
const perm = await permissionFor(root, false);
|
|
@@ -3102,11 +3504,23 @@ async function openFsCampaign(input) {
|
|
|
3102
3504
|
}
|
|
3103
3505
|
}
|
|
3104
3506
|
function closeFsCampaign() {
|
|
3507
|
+
if (getFsSyncEngine() === "download") void persistDownloadCampaign(false);
|
|
3105
3508
|
campaignDir = notesDir = shotsDir = null;
|
|
3106
3509
|
campaign = null;
|
|
3107
3510
|
noteIndex = {};
|
|
3511
|
+
if (getFsSyncEngine() === "download") {
|
|
3512
|
+
setState("connected");
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3108
3515
|
if (root) setState("connected");
|
|
3109
3516
|
}
|
|
3517
|
+
async function persistDownloadCampaign(active = true) {
|
|
3518
|
+
if (!metaStore || !campaign) return;
|
|
3519
|
+
try {
|
|
3520
|
+
await metaStore.setMeta(FS_CAMPAIGN_META_KEY, { campaign, notes: noteIndex, active });
|
|
3521
|
+
} catch {
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3110
3524
|
async function writeFile(dir, name, data) {
|
|
3111
3525
|
const handle = await dir.getFileHandle(name, { create: true });
|
|
3112
3526
|
const writable = await handle.createWritable();
|
|
@@ -3137,6 +3551,13 @@ function entryFor(note) {
|
|
|
3137
3551
|
};
|
|
3138
3552
|
}
|
|
3139
3553
|
async function syncNoteToDisk(note, allNotes) {
|
|
3554
|
+
if (getFsSyncEngine() === "download") {
|
|
3555
|
+
if (!campaign) return false;
|
|
3556
|
+
noteIndex[note.id] = entryFor(note);
|
|
3557
|
+
await persistDownloadCampaign();
|
|
3558
|
+
setState("syncing");
|
|
3559
|
+
return true;
|
|
3560
|
+
}
|
|
3140
3561
|
if (!campaignDir || !notesDir || !shotsDir || !campaign) return false;
|
|
3141
3562
|
try {
|
|
3142
3563
|
const previous = noteIndex[note.id];
|
|
@@ -3164,6 +3585,12 @@ async function syncNoteToDisk(note, allNotes) {
|
|
|
3164
3585
|
}
|
|
3165
3586
|
}
|
|
3166
3587
|
async function removeNoteFromDisk(noteId, allNotes) {
|
|
3588
|
+
if (getFsSyncEngine() === "download") {
|
|
3589
|
+
if (!noteIndex[noteId]) return true;
|
|
3590
|
+
delete noteIndex[noteId];
|
|
3591
|
+
await persistDownloadCampaign();
|
|
3592
|
+
return true;
|
|
3593
|
+
}
|
|
3167
3594
|
if (!campaignDir || !notesDir || !shotsDir) return false;
|
|
3168
3595
|
const entry = noteIndex[noteId];
|
|
3169
3596
|
if (!entry) return true;
|
|
@@ -3189,8 +3616,15 @@ function noteMarkdownForDisk(note, entry) {
|
|
|
3189
3616
|
}
|
|
3190
3617
|
async function flushCampaignFiles(allNotes) {
|
|
3191
3618
|
if (!campaignDir || !campaign) return;
|
|
3192
|
-
|
|
3193
|
-
|
|
3619
|
+
await writeFile(campaignDir, "campaign.json", campaignJsonText(allNotes));
|
|
3620
|
+
await writeFile(campaignDir, "REPORT.md", reportMarkdownText(allNotes));
|
|
3621
|
+
}
|
|
3622
|
+
function orderedKnownNotes(allNotes) {
|
|
3623
|
+
return allNotes.filter((n) => noteIndex[n.id]).slice().sort((a, b) => (noteIndex[a.id].seq ?? 0) - (noteIndex[b.id].seq ?? 0));
|
|
3624
|
+
}
|
|
3625
|
+
function campaignJsonText(allNotes) {
|
|
3626
|
+
if (!campaign) return "{}";
|
|
3627
|
+
const ordered = orderedKnownNotes(allNotes);
|
|
3194
3628
|
const meta = {
|
|
3195
3629
|
project: campaign.project,
|
|
3196
3630
|
campaign: campaign.campaign,
|
|
@@ -3200,7 +3634,11 @@ async function flushCampaignFiles(allNotes) {
|
|
|
3200
3634
|
noteCount: ordered.length,
|
|
3201
3635
|
notes: noteIndex
|
|
3202
3636
|
};
|
|
3203
|
-
|
|
3637
|
+
return JSON.stringify(meta, null, 2);
|
|
3638
|
+
}
|
|
3639
|
+
function reportMarkdownText(allNotes) {
|
|
3640
|
+
if (!campaign) return "";
|
|
3641
|
+
const ordered = orderedKnownNotes(allNotes);
|
|
3204
3642
|
const counts = { bug: 0, question: 0, polish: 0, verified: 0 };
|
|
3205
3643
|
for (const n of ordered) {
|
|
3206
3644
|
const sev = n.severity ?? "bug";
|
|
@@ -3214,20 +3652,66 @@ async function flushCampaignFiles(allNotes) {
|
|
|
3214
3652
|
"",
|
|
3215
3653
|
campaign.tester ? `Tester: ${campaign.tester} ` : "",
|
|
3216
3654
|
`Started: ${campaign.startedAt} `,
|
|
3217
|
-
`Updated: ${
|
|
3655
|
+
`Updated: ${(/* @__PURE__ */ new Date()).toISOString()} `,
|
|
3218
3656
|
`Points: ${ordered.length} (${counts.bug} bug, ${counts.question} question, ${counts.polish} polish \xB7 ${counts.verified} verified)`,
|
|
3219
3657
|
"",
|
|
3220
|
-
"Written live by Qapture as each point was saved. Screenshots are in",
|
|
3658
|
+
getFsSyncEngine() === "download" ? "Saved by Qapture as the campaign was tested. Screenshots are in" : "Written live by Qapture as each point was saved. Screenshots are in",
|
|
3221
3659
|
"`screenshots/`, one Markdown file per point in `notes/`.",
|
|
3222
3660
|
"",
|
|
3223
3661
|
"---",
|
|
3224
3662
|
""
|
|
3225
3663
|
].filter((line) => line !== "").join("\n");
|
|
3226
3664
|
const body = ordered.map((n) => noteMarkdownForDisk(n, noteIndex[n.id])).join("\n---\n\n");
|
|
3227
|
-
|
|
3228
|
-
${body}
|
|
3665
|
+
return `${header}
|
|
3666
|
+
${body}`;
|
|
3667
|
+
}
|
|
3668
|
+
async function buildCampaignZipBlob(allNotes) {
|
|
3669
|
+
if (!campaign || typeof document === "undefined") return null;
|
|
3670
|
+
try {
|
|
3671
|
+
const { default: JSZip } = await import('jszip');
|
|
3672
|
+
const zip = new JSZip();
|
|
3673
|
+
const base = zip.folder(safeSegment(campaign.project, "Project"))?.folder(safeSegment(campaign.campaign, defaultCampaignName()));
|
|
3674
|
+
if (!base) return null;
|
|
3675
|
+
base.file("campaign.json", campaignJsonText(allNotes));
|
|
3676
|
+
base.file("REPORT.md", reportMarkdownText(allNotes));
|
|
3677
|
+
const notesFolder = base.folder("notes");
|
|
3678
|
+
const shotsFolder = base.folder("screenshots");
|
|
3679
|
+
for (const note of orderedKnownNotes(allNotes)) {
|
|
3680
|
+
const entry = noteIndex[note.id];
|
|
3681
|
+
notesFolder?.file(entry.file, noteMarkdownForDisk(note, entry));
|
|
3682
|
+
if (note.screenshot && entry.shot) shotsFolder?.file(entry.shot, note.screenshot);
|
|
3683
|
+
if (note.afterScreenshot && entry.after) shotsFolder?.file(entry.after, note.afterScreenshot);
|
|
3684
|
+
}
|
|
3685
|
+
return await zip.generateAsync({ type: "blob" });
|
|
3686
|
+
} catch (err) {
|
|
3687
|
+
setState("error", describeError(err));
|
|
3688
|
+
return null;
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
async function downloadCampaignZip(allNotes) {
|
|
3692
|
+
const blob = await buildCampaignZipBlob(allNotes);
|
|
3693
|
+
if (!blob) return false;
|
|
3694
|
+
const url = URL.createObjectURL(blob);
|
|
3695
|
+
const a = document.createElement("a");
|
|
3696
|
+
a.href = url;
|
|
3697
|
+
a.download = campaignZipFileName();
|
|
3698
|
+
document.body.appendChild(a);
|
|
3699
|
+
a.click();
|
|
3700
|
+
a.remove();
|
|
3701
|
+
setTimeout(() => URL.revokeObjectURL(url), 2e3);
|
|
3702
|
+
setState("syncing");
|
|
3703
|
+
return true;
|
|
3229
3704
|
}
|
|
3230
3705
|
async function syncAllToDisk(allNotes) {
|
|
3706
|
+
if (getFsSyncEngine() === "download") {
|
|
3707
|
+
if (!campaign) return false;
|
|
3708
|
+
for (const note of allNotes.slice().reverse()) {
|
|
3709
|
+
if (!noteIndex[note.id]) noteIndex[note.id] = entryFor(note);
|
|
3710
|
+
}
|
|
3711
|
+
await persistDownloadCampaign();
|
|
3712
|
+
setState("syncing");
|
|
3713
|
+
return true;
|
|
3714
|
+
}
|
|
3231
3715
|
if (!campaignDir) return false;
|
|
3232
3716
|
const ordered = allNotes.slice().reverse();
|
|
3233
3717
|
bulkWriting = true;
|
|
@@ -4023,6 +4507,15 @@ function QaProvider({
|
|
|
4023
4507
|
closeFsCampaign();
|
|
4024
4508
|
notify(t("sync_paused"), { id: "sync-state" });
|
|
4025
4509
|
}, [notify, t]);
|
|
4510
|
+
const saveSyncFolderNow = React.useCallback(async () => {
|
|
4511
|
+
const name = campaignZipFileName();
|
|
4512
|
+
const ok = await downloadCampaignZip(notesRef.current);
|
|
4513
|
+
notify(ok ? t("sync_zip_saved", { name }) : t("sync_zip_failed"), {
|
|
4514
|
+
tone: ok ? "success" : "error",
|
|
4515
|
+
id: "sync-zip"
|
|
4516
|
+
});
|
|
4517
|
+
return ok;
|
|
4518
|
+
}, [notify, t]);
|
|
4026
4519
|
const forgetSyncFolder = React.useCallback(async () => {
|
|
4027
4520
|
await disconnectFsSync(idb);
|
|
4028
4521
|
storage.setJSON(LAST_CAMPAIGN_KEY, {});
|
|
@@ -4071,10 +4564,23 @@ function QaProvider({
|
|
|
4071
4564
|
React.useEffect(() => {
|
|
4072
4565
|
if (!autoBackup) return;
|
|
4073
4566
|
if (notes.length === 0 || isExporting) return;
|
|
4074
|
-
|
|
4567
|
+
const syncing = getFsSyncState() === "syncing";
|
|
4568
|
+
const viaDownload = getFsSyncEngine() === "download";
|
|
4569
|
+
if (syncing && !viaDownload) return;
|
|
4075
4570
|
const lastAt = Number(storage.getItem(AUTO_BACKUP_AT_KEY) ?? "0") || 0;
|
|
4076
4571
|
if (notes.length < lastAt + AUTO_BACKUP_EVERY) return;
|
|
4077
4572
|
storage.setItem(AUTO_BACKUP_AT_KEY, String(notes.length));
|
|
4573
|
+
if (syncing && viaDownload) {
|
|
4574
|
+
const name = campaignZipFileName();
|
|
4575
|
+
void downloadCampaignZip(notesRef.current).then((ok) => {
|
|
4576
|
+
notify(ok ? t("sync_zip_saved", { name }) : t("sync_zip_failed"), {
|
|
4577
|
+
tone: ok ? "success" : "error",
|
|
4578
|
+
id: "sync-zip",
|
|
4579
|
+
duration: 3e3
|
|
4580
|
+
});
|
|
4581
|
+
});
|
|
4582
|
+
return;
|
|
4583
|
+
}
|
|
4078
4584
|
const stamp = nowIso().slice(0, 16).replace(/[:T]/g, "-");
|
|
4079
4585
|
void exportZipRef.current(`qa-autosave-${stamp}-${notes.length}`).then(() => {
|
|
4080
4586
|
notify(t("autosave_done", { n: notes.length }), { id: "autosave", duration: 3e3 });
|
|
@@ -4375,6 +4881,7 @@ function QaProvider({
|
|
|
4375
4881
|
}, []);
|
|
4376
4882
|
React.useEffect(() => {
|
|
4377
4883
|
if (typeof window === "undefined") return;
|
|
4884
|
+
if (notesLoading) return;
|
|
4378
4885
|
const params = new URLSearchParams(window.location.search);
|
|
4379
4886
|
const raw = params.get("qa");
|
|
4380
4887
|
if (!raw || !raw.startsWith("walk")) return;
|
|
@@ -4393,7 +4900,7 @@ function QaProvider({
|
|
|
4393
4900
|
} else {
|
|
4394
4901
|
startWalk("plan", 0);
|
|
4395
4902
|
}
|
|
4396
|
-
}, [startWalk]);
|
|
4903
|
+
}, [startWalk, notesLoading]);
|
|
4397
4904
|
const value = {
|
|
4398
4905
|
// Data
|
|
4399
4906
|
notes,
|
|
@@ -4414,7 +4921,10 @@ function QaProvider({
|
|
|
4414
4921
|
brand: config.brand,
|
|
4415
4922
|
loginField: config.loginField,
|
|
4416
4923
|
credentials: config.credentials,
|
|
4417
|
-
|
|
4924
|
+
// Never the raw config value: an unconfigured project falls back to the
|
|
4925
|
+
// generic plan so the Guide is never a blank page.
|
|
4926
|
+
journey: journeyOrEmpty(config.journey),
|
|
4927
|
+
journeyIsGeneric: !config.journey?.some((lane) => lane.steps.length > 0),
|
|
4418
4928
|
preamble: config.preamble,
|
|
4419
4929
|
// i18n helpers
|
|
4420
4930
|
t,
|
|
@@ -4462,7 +4972,8 @@ function QaProvider({
|
|
|
4462
4972
|
// rebuilt when fsSync's module state changes (path/campaign live outside
|
|
4463
4973
|
// React).
|
|
4464
4974
|
sync: {
|
|
4465
|
-
supported:
|
|
4975
|
+
supported: isFsSyncAvailable(),
|
|
4976
|
+
engine: getFsSyncEngine(),
|
|
4466
4977
|
state: syncState,
|
|
4467
4978
|
path: syncTick >= 0 ? getFsSyncPath() : "",
|
|
4468
4979
|
campaign: getFsSyncCampaign(),
|
|
@@ -4472,6 +4983,7 @@ function QaProvider({
|
|
|
4472
4983
|
reconnectSyncFolder,
|
|
4473
4984
|
startSyncCampaign,
|
|
4474
4985
|
stopSyncCampaign,
|
|
4986
|
+
saveSyncFolderNow,
|
|
4475
4987
|
forgetSyncFolder,
|
|
4476
4988
|
suggestCampaignName,
|
|
4477
4989
|
lastCampaign,
|
|
@@ -4516,7 +5028,9 @@ function QaProvider({
|
|
|
4516
5028
|
return /* @__PURE__ */ jsxRuntime.jsx(QaContext.Provider, { value, children });
|
|
4517
5029
|
}
|
|
4518
5030
|
function journeyOrEmpty(journey) {
|
|
4519
|
-
|
|
5031
|
+
const configured = Array.isArray(journey) ? journey : [];
|
|
5032
|
+
const hasSteps = configured.some((lane) => lane.steps.length > 0);
|
|
5033
|
+
return hasSteps ? configured : FALLBACK_JOURNEY;
|
|
4520
5034
|
}
|
|
4521
5035
|
function useQa() {
|
|
4522
5036
|
const ctx = React.useContext(QaContext);
|
|
@@ -6497,6 +7011,7 @@ function SettingsSheet({ onClose }) {
|
|
|
6497
7011
|
forgetSyncFolder,
|
|
6498
7012
|
suggestCampaignName,
|
|
6499
7013
|
lastCampaign,
|
|
7014
|
+
saveSyncFolderNow,
|
|
6500
7015
|
storageHealth,
|
|
6501
7016
|
refreshStorageHealth,
|
|
6502
7017
|
requestPersistentStorage: requestPersistentStorage2,
|
|
@@ -6538,6 +7053,7 @@ function SettingsSheet({ onClose }) {
|
|
|
6538
7053
|
}
|
|
6539
7054
|
};
|
|
6540
7055
|
const syncing = sync.state === "syncing";
|
|
7056
|
+
const viaZip = sync.engine === "download";
|
|
6541
7057
|
const quotaKnown = storageHealth.supported && storageHealth.quotaBytes > 0;
|
|
6542
7058
|
const usedPct = quotaKnown ? Math.min(100, Math.round(storageHealth.ratio * 100)) : 0;
|
|
6543
7059
|
const meterColor = storageHealth.level === "critical" ? "var(--qa-danger)" : storageHealth.level === "warn" ? "var(--qa-warn)" : "var(--qa-accent)";
|
|
@@ -6565,6 +7081,7 @@ function SettingsSheet({ onClose }) {
|
|
|
6565
7081
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-flex-1 qa-space-y-4 qa-overflow-y-auto qa-p-3", children: [
|
|
6566
7082
|
/* @__PURE__ */ jsxRuntime.jsxs(Section, { icon: "Folder", title: t("sync_title"), children: [
|
|
6567
7083
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo qa-leading-relaxed", children: t("sync_hint") }),
|
|
7084
|
+
viaZip ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-rounded-lg qa-border qa-border-dashed qa-border-subtle qa-p-2 qa-text-10 qa-text-mid qa-leading-relaxed", children: t("sync_zip_mode") }) : null,
|
|
6568
7085
|
!sync.supported ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-rounded-lg qa-border qa-border-dashed qa-border-subtle qa-p-2 qa-text-10 qa-text-mid", children: t("sync_unsupported") }) : sync.state === "off" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6569
7086
|
"button",
|
|
6570
7087
|
{
|
|
@@ -6592,24 +7109,41 @@ function SettingsSheet({ onClose }) {
|
|
|
6592
7109
|
]
|
|
6593
7110
|
}
|
|
6594
7111
|
) : syncing ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-space-y-2", children: [
|
|
7112
|
+
viaZip && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-m-0 qa-text-10 qa-text-lo", children: t("sync_zip_where") }),
|
|
6595
7113
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "qa-m-0 qa-flex qa-items-center qa-gap-1.5 qa-rounded-lg qa-bg-success-tint qa-text-success qa-px-2 qa-py-1.5 qa-text-10", children: [
|
|
6596
7114
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "FolderCheck", size: 13, className: "qa-shrink-0" }),
|
|
6597
7115
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-truncate qa-dir-ltr", dir: "ltr", title: sync.path, children: sync.path })
|
|
6598
7116
|
] }),
|
|
6599
|
-
/* @__PURE__ */ jsxRuntime.
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
7117
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-flex qa-flex-wrap qa-gap-2", children: [
|
|
7118
|
+
viaZip && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
7119
|
+
"button",
|
|
7120
|
+
{
|
|
7121
|
+
type: "button",
|
|
7122
|
+
disabled: busy,
|
|
7123
|
+
onClick: () => void run2(saveSyncFolderNow),
|
|
7124
|
+
className: "qa-tap qa-inline-flex qa-items-center qa-gap-1.5 qa-rounded-lg qa-bg-accent qa-px-3 qa-py-1.5 qa-text-xs qa-font-semibold",
|
|
7125
|
+
style: { border: "none", cursor: "pointer" },
|
|
7126
|
+
children: [
|
|
7127
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Download", size: 13 }),
|
|
7128
|
+
t("sync_zip_save_now")
|
|
7129
|
+
]
|
|
7130
|
+
}
|
|
7131
|
+
),
|
|
7132
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
7133
|
+
"button",
|
|
7134
|
+
{
|
|
7135
|
+
type: "button",
|
|
7136
|
+
onClick: stopSyncCampaign,
|
|
7137
|
+
className: "qa-tap qa-rounded-lg qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-10 qa-text-mid",
|
|
7138
|
+
style: { background: "transparent", cursor: "pointer" },
|
|
7139
|
+
children: t("sync_stop")
|
|
7140
|
+
}
|
|
7141
|
+
)
|
|
7142
|
+
] })
|
|
6609
7143
|
] }) : (
|
|
6610
7144
|
/* connected: folder chosen, campaign not open yet */
|
|
6611
7145
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-space-y-2", children: [
|
|
6612
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "qa-m-0 qa-flex qa-items-center qa-gap-1.5 qa-text-10 qa-text-mid", children: [
|
|
7146
|
+
!viaZip && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "qa-m-0 qa-flex qa-items-center qa-gap-1.5 qa-text-10 qa-text-mid", children: [
|
|
6613
7147
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Folder", size: 12, className: "qa-shrink-0" }),
|
|
6614
7148
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "qa-truncate qa-dir-ltr", dir: "ltr", children: sync.path })
|
|
6615
7149
|
] }),
|
|
@@ -6631,11 +7165,11 @@ function SettingsSheet({ onClose }) {
|
|
|
6631
7165
|
style: { border: "none", cursor: "pointer" },
|
|
6632
7166
|
children: [
|
|
6633
7167
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "Check", size: 13 }),
|
|
6634
|
-
t("sync_start")
|
|
7168
|
+
viaZip ? t("sync_zip_start") : t("sync_start")
|
|
6635
7169
|
]
|
|
6636
7170
|
}
|
|
6637
7171
|
),
|
|
6638
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
7172
|
+
!viaZip && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6639
7173
|
"button",
|
|
6640
7174
|
{
|
|
6641
7175
|
type: "button",
|
|
@@ -7145,12 +7679,31 @@ function Lane({
|
|
|
7145
7679
|
] });
|
|
7146
7680
|
}
|
|
7147
7681
|
function GuideSection() {
|
|
7148
|
-
const {
|
|
7682
|
+
const {
|
|
7683
|
+
guideChecked,
|
|
7684
|
+
toggleGuide,
|
|
7685
|
+
t,
|
|
7686
|
+
journey,
|
|
7687
|
+
journeyIsGeneric,
|
|
7688
|
+
pick: pick2,
|
|
7689
|
+
lang,
|
|
7690
|
+
startTestAlong,
|
|
7691
|
+
startWalk,
|
|
7692
|
+
testAlongSteps
|
|
7693
|
+
} = useQa();
|
|
7149
7694
|
const all = journey.flatMap((g) => g.steps.map((s) => keyOf(g.id, s.path)));
|
|
7150
7695
|
const done = all.filter((k) => guideChecked.has(k)).length;
|
|
7151
7696
|
const pct = all.length > 0 ? Math.round(done / all.length * 100) : 0;
|
|
7152
7697
|
const coverage = computeCoverage(journey, guideChecked);
|
|
7153
7698
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-space-y-3", children: [
|
|
7699
|
+
journeyIsGeneric && /* @__PURE__ */ jsxRuntime.jsx(
|
|
7700
|
+
"p",
|
|
7701
|
+
{
|
|
7702
|
+
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",
|
|
7703
|
+
"data-qa-generic-plan": "true",
|
|
7704
|
+
children: t("journey_generic")
|
|
7705
|
+
}
|
|
7706
|
+
),
|
|
7154
7707
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "qa-rounded-xl qa-border qa-border-accent qa-bg-accent-tint qa-p-3 qa-elev-1", children: [
|
|
7155
7708
|
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: [
|
|
7156
7709
|
/* @__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" }),
|
|
@@ -7207,8 +7760,7 @@ function GuideSection() {
|
|
|
7207
7760
|
pick: pick2
|
|
7208
7761
|
},
|
|
7209
7762
|
g.id
|
|
7210
|
-
))
|
|
7211
|
-
journey.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "qa-py-8 qa-text-center qa-text-sm qa-text-slate-400", children: t("tab_guide") })
|
|
7763
|
+
))
|
|
7212
7764
|
] });
|
|
7213
7765
|
}
|
|
7214
7766
|
var TABS = [
|
|
@@ -8053,7 +8605,7 @@ function WalkHud() {
|
|
|
8053
8605
|
)
|
|
8054
8606
|
] });
|
|
8055
8607
|
}
|
|
8056
|
-
const onThisPage = typeof window !== "undefined" && stop.path === window.location.pathname;
|
|
8608
|
+
const onThisPage = stop.path === ANY_PAGE || typeof window !== "undefined" && stop.path === window.location.pathname;
|
|
8057
8609
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8058
8610
|
"div",
|
|
8059
8611
|
{
|
|
@@ -9151,6 +9703,7 @@ function CaptureMode() {
|
|
|
9151
9703
|
type: "button",
|
|
9152
9704
|
onClick: () => selection && void runCapture(selection.rect),
|
|
9153
9705
|
className: "qa-tap qa-inline-flex qa-items-center qa-gap-1.5 qa-rounded-md qa-border qa-border-subtle qa-px-2 qa-py-1 qa-text-xs qa-text-mid qa-focus-ring",
|
|
9706
|
+
style: { background: "transparent", cursor: "pointer" },
|
|
9154
9707
|
children: [
|
|
9155
9708
|
/* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "RotateCcw", size: 13 }),
|
|
9156
9709
|
t("retry")
|
|
@@ -9452,5 +10005,5 @@ function Qapture({ config }) {
|
|
|
9452
10005
|
exports.Qapture = Qapture;
|
|
9453
10006
|
exports.deleteQaDatabase = deleteQaDatabase;
|
|
9454
10007
|
exports.initQaStudio = initQaStudio;
|
|
9455
|
-
//# sourceMappingURL=chunk-
|
|
9456
|
-
//# sourceMappingURL=chunk-
|
|
10008
|
+
//# sourceMappingURL=chunk-CIFJMD2B.cjs.map
|
|
10009
|
+
//# sourceMappingURL=chunk-CIFJMD2B.cjs.map
|