@designfever/web-review-kit 0.3.0 → 0.4.0

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.
@@ -3,7 +3,7 @@ import {
3
3
  createWebReviewKit,
4
4
  getNumberedReviewItems,
5
5
  normalizeReviewItemStatus
6
- } from "./chunk-I76WEDLA.js";
6
+ } from "./chunk-QFNYQCTA.js";
7
7
 
8
8
  // src/react-shell.tsx
9
9
  import React2 from "react";
@@ -148,6 +148,7 @@ function ensureReviewShellStyle() {
148
148
  --df-review-shadow-device: 0 24px 60px rgba(0, 0, 0, 0.38);
149
149
  --df-review-shadow-panel: 0 18px 48px rgba(0, 0, 0, 0.38);
150
150
  --df-review-shadow-modal: 0 24px 70px rgba(0, 0, 0, 0.48);
151
+ --df-review-select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23d7e0ec' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
151
152
 
152
153
  /* Semantic aliases consumed by the existing shell chrome. */
153
154
  --df-review-bg: var(--df-review-color-canvas);
@@ -243,6 +244,7 @@ function ensureReviewShellStyle() {
243
244
  --df-review-shadow-device: 0 24px 60px rgba(15, 23, 42, 0.18);
244
245
  --df-review-shadow-panel: 0 18px 48px rgba(15, 23, 42, 0.18);
245
246
  --df-review-shadow-modal: 0 24px 70px rgba(15, 23, 42, 0.2);
247
+ --df-review-select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2317202c' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
246
248
  }
247
249
 
248
250
  button,
@@ -260,7 +262,7 @@ function ensureReviewShellStyle() {
260
262
  --df-review-frame-gutter-x: var(--df-review-space-4);
261
263
  display: grid;
262
264
  grid-template-columns: minmax(0, 1fr) 0 32px;
263
- grid-template-rows: auto minmax(0, 1fr);
265
+ grid-template-rows: auto auto minmax(0, 1fr);
264
266
  width: 100%;
265
267
  height: 100%;
266
268
  overflow: hidden;
@@ -287,6 +289,20 @@ function ensureReviewShellStyle() {
287
289
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.025);
288
290
  }
289
291
 
292
+ .df-review-presence-row {
293
+ grid-column: 1;
294
+ grid-row: 2;
295
+ position: relative;
296
+ z-index: 590;
297
+ display: flex;
298
+ align-items: center;
299
+ justify-content: flex-start;
300
+ min-width: 0;
301
+ padding: var(--df-review-space-2) var(--df-review-frame-gutter-x);
302
+ border-bottom: 1px solid var(--df-review-line-soft);
303
+ background: var(--df-review-panel);
304
+ }
305
+
290
306
  .df-review-address {
291
307
  display: grid;
292
308
  grid-template-columns: auto minmax(160px, 1fr) auto auto;
@@ -347,7 +363,7 @@ function ensureReviewShellStyle() {
347
363
  .df-review-prompt-block-header button:hover,
348
364
  .df-review-item-actions button:hover,
349
365
  .df-review-item-visibility:hover,
350
- .df-review-item-source-open:hover,
366
+ .df-review-item-link-copy:hover,
351
367
  .df-review-item-prompt-copy:hover,
352
368
  .df-review-item-delete:hover,
353
369
  .df-review-presets button.is-active,
@@ -410,7 +426,7 @@ function ensureReviewShellStyle() {
410
426
  z-index: 1;
411
427
  display: grid;
412
428
  grid-template-rows: auto minmax(0, 1fr);
413
- width: min(760px, calc(100vw - 48px));
429
+ width: min(940px, calc(100vw - 48px));
414
430
  max-height: min(720px, calc(100vh - 48px));
415
431
  overflow: hidden;
416
432
  border: 1px solid var(--df-review-line);
@@ -465,7 +481,31 @@ function ensureReviewShellStyle() {
465
481
  background: var(--df-review-control-hover);
466
482
  }
467
483
 
484
+ .df-review-sitemap-controls {
485
+ display: flex;
486
+ align-items: center;
487
+ gap: 8px;
488
+ min-width: 0;
489
+ padding: 10px 12px;
490
+ border-bottom: 1px solid var(--df-review-line-soft);
491
+ background: var(--df-review-panel);
492
+ }
493
+
494
+ .df-review-sitemap-controls select {
495
+ min-width: 138px;
496
+ min-height: 32px;
497
+ border: 1px solid var(--df-review-line-soft);
498
+ border-radius: var(--df-review-radius-sm);
499
+ padding: 0 26px 0 10px;
500
+ color: var(--df-review-text);
501
+ background: var(--df-review-control);
502
+ box-shadow: var(--df-review-shadow-control);
503
+ font-size: var(--df-review-font-size-xs);
504
+ font-weight: 850;
505
+ }
506
+
468
507
  .df-review-sitemap-list {
508
+ --df-review-sitemap-grid-template: minmax(190px, 1fr) 74px 78px 64px minmax(108px, 160px);
469
509
  display: grid;
470
510
  align-content: start;
471
511
  min-height: 0;
@@ -476,7 +516,7 @@ function ensureReviewShellStyle() {
476
516
  .df-review-sitemap-table-head,
477
517
  .df-review-sitemap-row {
478
518
  display: grid;
479
- grid-template-columns: minmax(160px, 1fr) 70px 78px minmax(96px, 140px);
519
+ grid-template-columns: var(--df-review-sitemap-grid-template);
480
520
  align-items: center;
481
521
  column-gap: 0;
482
522
  }
@@ -491,13 +531,54 @@ function ensureReviewShellStyle() {
491
531
  background: var(--df-review-surface);
492
532
  color: var(--df-review-muted);
493
533
  font-size: var(--df-review-font-size-xs);
494
- font-weight: 900;
495
- letter-spacing: 0.04em;
534
+ font-weight: 720;
535
+ letter-spacing: 0.03em;
496
536
  text-transform: uppercase;
497
537
  }
498
538
 
499
- .df-review-sitemap-table-head span:not(:first-child) {
539
+ .df-review-sitemap-sort {
540
+ display: inline-flex;
541
+ align-items: center;
542
+ justify-content: flex-end;
543
+ gap: 4px;
544
+ min-width: 0;
545
+ min-height: 28px;
546
+ border: 0;
547
+ border-radius: 0;
548
+ padding: 0 0 0 8px;
549
+ color: inherit;
550
+ background: transparent;
551
+ box-shadow: none;
552
+ font: inherit;
553
+ letter-spacing: inherit;
500
554
  text-align: right;
555
+ text-transform: inherit;
556
+ cursor: pointer;
557
+ }
558
+
559
+ .df-review-sitemap-sort.is-page {
560
+ justify-content: flex-start;
561
+ padding-left: 0;
562
+ text-align: left;
563
+ }
564
+
565
+ .df-review-sitemap-sort-indicator {
566
+ width: 8px;
567
+ min-width: 8px;
568
+ color: var(--df-review-accent);
569
+ text-align: center;
570
+ }
571
+
572
+ .df-review-sitemap-sort-label {
573
+ min-width: 0;
574
+ overflow: hidden;
575
+ text-overflow: ellipsis;
576
+ white-space: nowrap;
577
+ }
578
+
579
+ .df-review-sitemap-sort:hover,
580
+ .df-review-sitemap-sort.is-active {
581
+ color: var(--df-review-text);
501
582
  }
502
583
 
503
584
  .df-review-sitemap-row {
@@ -515,6 +596,15 @@ function ensureReviewShellStyle() {
515
596
  cursor: pointer;
516
597
  }
517
598
 
599
+ .df-review-sitemap-row.is-summary {
600
+ position: sticky;
601
+ bottom: 0;
602
+ z-index: 1;
603
+ border-top: 1px solid var(--df-review-line);
604
+ border-bottom: 0;
605
+ background: var(--df-review-surface);
606
+ }
607
+
518
608
  .df-review-sitemap-row.is-folder {
519
609
  cursor: default;
520
610
  }
@@ -531,11 +621,12 @@ function ensureReviewShellStyle() {
531
621
  .df-review-sitemap-path {
532
622
  display: inline-flex;
533
623
  align-items: center;
624
+ gap: 7px;
534
625
  min-width: 0;
535
626
  overflow-wrap: anywhere;
536
627
  color: var(--df-review-text);
537
628
  font-size: var(--df-review-font-size-md);
538
- font-weight: 800;
629
+ font-weight: 650;
539
630
  line-height: 1.35;
540
631
  }
541
632
 
@@ -547,26 +638,33 @@ function ensureReviewShellStyle() {
547
638
  flex: 0 0 auto;
548
639
  color: var(--df-review-muted);
549
640
  font-family: var(--df-review-font-mono);
550
- font-weight: 700;
641
+ font-weight: 500;
551
642
  white-space: pre;
552
643
  }
553
644
 
645
+ .df-review-sitemap-label {
646
+ min-width: 0;
647
+ overflow: hidden;
648
+ text-overflow: ellipsis;
649
+ }
650
+
554
651
  .df-review-sitemap-cell {
555
652
  min-width: 0;
556
653
  color: var(--df-review-muted);
557
654
  font-size: var(--df-review-font-size-sm);
558
655
  font-variant-numeric: tabular-nums;
559
- font-weight: 900;
656
+ font-weight: 650;
560
657
  line-height: 1;
561
658
  text-align: right;
562
659
  }
563
660
 
564
- .df-review-sitemap-cell.is-local {
661
+ .df-review-sitemap-cell.is-total {
565
662
  color: var(--df-review-accent);
663
+ font-weight: 760;
566
664
  }
567
665
 
568
- .df-review-sitemap-cell.is-remote {
569
- color: var(--df-review-area);
666
+ .df-review-sitemap-cell.is-total strong {
667
+ font: inherit;
570
668
  }
571
669
 
572
670
  .df-review-sitemap-cell.is-online {
@@ -1379,7 +1477,7 @@ function ensureReviewShellStyle() {
1379
1477
 
1380
1478
  .df-review-side-rail {
1381
1479
  grid-column: 3;
1382
- grid-row: 1 / span 2;
1480
+ grid-row: 1 / span 3;
1383
1481
  position: relative;
1384
1482
  z-index: 600;
1385
1483
  display: flex;
@@ -1439,7 +1537,7 @@ function ensureReviewShellStyle() {
1439
1537
 
1440
1538
  .df-review-qa-panel {
1441
1539
  grid-column: 2;
1442
- grid-row: 1 / span 2;
1540
+ grid-row: 1 / span 3;
1443
1541
  position: relative;
1444
1542
  z-index: 600;
1445
1543
  display: grid;
@@ -1512,101 +1610,114 @@ function ensureReviewShellStyle() {
1512
1610
  font-variant-numeric: tabular-nums;
1513
1611
  }
1514
1612
 
1515
- .df-review-presence-row {
1516
- display: grid;
1517
- grid-template-columns: minmax(0, 1fr);
1518
- align-items: start;
1613
+ .df-review-presence-overlay {
1614
+ display: flex;
1615
+ align-items: center;
1616
+ flex-wrap: wrap;
1519
1617
  gap: 5px;
1520
- min-width: 0;
1618
+ max-width: 100%;
1619
+ pointer-events: none;
1620
+ }
1621
+
1622
+ .df-review-presence-row .df-review-presence-overlay {
1623
+ width: 100%;
1624
+ max-width: 1440px;
1625
+ margin: 0 auto;
1521
1626
  }
1522
1627
 
1523
- .df-review-presence-label {
1628
+ .df-review-presence-chip {
1629
+ --df-review-presence-color: var(--df-review-accent);
1524
1630
  display: inline-flex;
1525
1631
  align-items: center;
1526
- gap: 4px;
1527
1632
  min-width: 0;
1528
- color: var(--df-review-muted);
1633
+ max-width: min(220px, calc(100% - 20px));
1634
+ min-height: 24px;
1635
+ border: 1px solid rgba(15, 23, 42, 0.12);
1636
+ border-radius: var(--df-review-radius-pill);
1637
+ padding: 0 8px;
1638
+ color: #17202c;
1639
+ background: rgba(255, 255, 255, 0.92);
1640
+ box-shadow: 0 8px 22px rgba(15, 23, 42, 0.14);
1529
1641
  font-size: var(--df-review-font-size-2xs);
1530
1642
  font-weight: 900;
1531
- line-height: 1;
1643
+ line-height: 1.1;
1644
+ overflow: hidden;
1645
+ text-overflow: ellipsis;
1532
1646
  white-space: nowrap;
1647
+ backdrop-filter: blur(8px);
1533
1648
  }
1534
1649
 
1535
- .df-review-presence-label svg {
1536
- width: 12px;
1537
- height: 12px;
1538
- fill: none;
1539
- stroke: currentColor;
1540
- stroke-linecap: round;
1541
- stroke-linejoin: round;
1542
- stroke-width: 2;
1650
+ .df-review-presence-row .df-review-presence-chip {
1651
+ max-width: none;
1543
1652
  }
1544
1653
 
1545
- .df-review-presence-list {
1546
- display: flex;
1547
- align-items: center;
1548
- gap: 4px;
1549
- min-width: 0;
1550
- flex-wrap: wrap;
1654
+ .df-review-presence-chip.is-self {
1655
+ border-color: var(--df-review-presence-color);
1656
+ box-shadow:
1657
+ inset 0 0 0 1px var(--df-review-presence-color),
1658
+ 0 8px 22px rgba(15, 23, 42, 0.14);
1551
1659
  }
1552
1660
 
1553
- .df-review-presence-chip {
1554
- --df-review-presence-color: var(--df-review-accent);
1661
+ .df-review-presence-more {
1555
1662
  display: inline-flex;
1556
1663
  align-items: center;
1557
- gap: 5px;
1558
- min-width: 0;
1559
- flex: 0 1 auto;
1560
- min-height: 22px;
1561
- border: 1px solid var(--df-review-line-soft);
1664
+ justify-content: center;
1665
+ min-width: 28px;
1666
+ min-height: 24px;
1667
+ border: 1px solid rgba(15, 23, 42, 0.12);
1562
1668
  border-radius: var(--df-review-radius-pill);
1563
- padding: 0 7px;
1564
- color: var(--df-review-text);
1565
- background: var(--df-review-chip-bg);
1669
+ padding: 0 8px;
1670
+ color: #17202c;
1671
+ background: rgba(255, 255, 255, 0.92);
1672
+ box-shadow: 0 8px 22px rgba(15, 23, 42, 0.14);
1673
+ cursor: pointer;
1674
+ font: inherit;
1566
1675
  font-size: var(--df-review-font-size-2xs);
1567
1676
  font-weight: 900;
1568
- line-height: 1.1;
1569
- white-space: nowrap;
1677
+ line-height: 1;
1678
+ pointer-events: auto;
1679
+ backdrop-filter: blur(8px);
1570
1680
  }
1571
1681
 
1572
- .df-review-presence-chip.is-self {
1573
- border-color: var(--df-review-presence-color);
1574
- background: var(--df-review-accent-soft);
1682
+ .df-review-presence-more:hover {
1683
+ border-color: rgba(0, 102, 255, 0.42);
1684
+ color: #005be8;
1685
+ background: rgba(255, 255, 255, 0.98);
1575
1686
  }
1576
1687
 
1577
- .df-review-presence-dot {
1578
- width: 7px;
1579
- min-width: 7px;
1580
- height: 7px;
1581
- border-radius: var(--df-review-radius-pill);
1582
- background: var(--df-review-presence-color);
1688
+ .df-review-list-controls {
1689
+ display: flex;
1690
+ align-items: center;
1691
+ gap: 4px;
1583
1692
  }
1584
1693
 
1585
- .df-review-presence-name {
1586
- min-width: 0;
1694
+ .df-review-source-select,
1695
+ .df-review-status-filter-select {
1696
+ min-height: 30px;
1697
+ border: 1px solid var(--df-review-line-soft);
1698
+ border-radius: var(--df-review-radius-sm);
1699
+ padding: 0 24px 0 8px;
1700
+ color: var(--df-review-text);
1701
+ background: var(--df-review-control);
1702
+ box-shadow: var(--df-review-shadow-control);
1703
+ font-size: var(--df-review-font-size-xs);
1704
+ font-weight: 800;
1587
1705
  }
1588
1706
 
1589
- .df-review-list-controls {
1590
- display: flex;
1591
- align-items: center;
1592
- gap: 4px;
1593
- }
1707
+ .df-review-source-select {
1708
+ width: 104px;
1709
+ }
1594
1710
 
1595
- .df-review-source-select {
1596
- width: 104px;
1597
- min-height: 30px;
1598
- border: 1px solid var(--df-review-line-soft);
1599
- border-radius: var(--df-review-radius-sm);
1600
- padding: 0 24px 0 8px;
1601
- color: var(--df-review-text);
1602
- background: var(--df-review-control);
1603
- box-shadow: var(--df-review-shadow-control);
1604
- font-size: var(--df-review-font-size-xs);
1605
- font-weight: 800;
1606
- }
1711
+ .df-review-status-filter-select {
1712
+ width: 124px;
1713
+ }
1607
1714
 
1608
- .df-review-source-refresh {
1609
- position: relative;
1715
+ .df-review-list-title .df-review-status-filter-select {
1716
+ margin-left: auto;
1717
+ }
1718
+
1719
+ .df-review-source-refresh {
1720
+ position: relative;
1610
1721
  display: inline-grid;
1611
1722
  place-items: center;
1612
1723
  width: 30px;
@@ -1942,6 +2053,34 @@ function ensureReviewShellStyle() {
1942
2053
  background: var(--df-review-area-soft);
1943
2054
  }
1944
2055
 
2056
+ .df-review-source-select,
2057
+ .df-review-status-filter-select,
2058
+ .df-review-item-status-select,
2059
+ .df-review-item-status-select.is-status-todo,
2060
+ .df-review-item-status-select.is-status-doing,
2061
+ .df-review-item-status-select.is-status-review,
2062
+ .df-review-item-status-select.is-status-hold,
2063
+ .df-review-item-status-select.is-status-done,
2064
+ .df-review-sitemap-controls select {
2065
+ --df-review-select-padding-x: 12px;
2066
+ --df-review-select-chevron-size: 14px;
2067
+ --df-review-select-chevron-gap: 8px;
2068
+ appearance: none;
2069
+ -webkit-appearance: none;
2070
+ background-image: var(--df-review-select-chevron);
2071
+ background-repeat: no-repeat;
2072
+ background-position: right var(--df-review-select-padding-x) center;
2073
+ background-size:
2074
+ var(--df-review-select-chevron-size)
2075
+ var(--df-review-select-chevron-size);
2076
+ padding-right: calc(
2077
+ var(--df-review-select-padding-x) +
2078
+ var(--df-review-select-chevron-size) +
2079
+ var(--df-review-select-chevron-gap)
2080
+ );
2081
+ padding-left: var(--df-review-select-padding-x);
2082
+ }
2083
+
1945
2084
  .df-review-item-status-badge.is-error {
1946
2085
  border-color: rgba(255, 143, 97, 0.36);
1947
2086
  color: var(--df-review-danger);
@@ -1967,8 +2106,8 @@ function ensureReviewShellStyle() {
1967
2106
 
1968
2107
  .df-review-item-delete,
1969
2108
  .df-review-item-edit,
2109
+ .df-review-item-link-copy,
1970
2110
  .df-review-item-prompt-copy,
1971
- .df-review-item-source-open,
1972
2111
  .df-review-item-visibility {
1973
2112
  display: inline-grid;
1974
2113
  place-items: center;
@@ -1985,7 +2124,7 @@ function ensureReviewShellStyle() {
1985
2124
 
1986
2125
  .df-review-item-visibility:hover,
1987
2126
  .df-review-item-edit:hover,
1988
- .df-review-item-source-open:hover,
2127
+ .df-review-item-link-copy:hover,
1989
2128
  .df-review-item-prompt-copy:hover {
1990
2129
  border-color: rgba(124, 199, 255, 0.34);
1991
2130
  color: var(--df-review-accent);
@@ -2007,14 +2146,15 @@ function ensureReviewShellStyle() {
2007
2146
  color: var(--df-review-subtle);
2008
2147
  }
2009
2148
 
2149
+ .df-review-item-link-copy.is-copied,
2010
2150
  .df-review-item-prompt-copy.is-copied {
2011
2151
  color: var(--df-review-accent);
2012
2152
  }
2013
2153
 
2014
2154
  .df-review-item-delete svg,
2015
2155
  .df-review-item-edit svg,
2156
+ .df-review-item-link-copy svg,
2016
2157
  .df-review-item-prompt-copy svg,
2017
- .df-review-item-source-open svg,
2018
2158
  .df-review-item-visibility svg {
2019
2159
  width: 14px;
2020
2160
  height: 14px;
@@ -2037,6 +2177,15 @@ function ensureReviewShellStyle() {
2037
2177
  cursor: auto;
2038
2178
  }
2039
2179
 
2180
+ .df-review-item-prompt-actions {
2181
+ display: inline-flex;
2182
+ grid-column: 2;
2183
+ align-items: center;
2184
+ justify-self: end;
2185
+ min-width: 0;
2186
+ cursor: auto;
2187
+ }
2188
+
2040
2189
  .df-review-item-remote-actions {
2041
2190
  display: inline-flex;
2042
2191
  grid-column: 3;
@@ -2114,7 +2263,7 @@ function ensureReviewShellStyle() {
2114
2263
 
2115
2264
  .df-review-stage {
2116
2265
  grid-column: 1;
2117
- grid-row: 2;
2266
+ grid-row: 3;
2118
2267
  display: grid;
2119
2268
  min-width: 0;
2120
2269
  min-height: 0;
@@ -2153,9 +2302,17 @@ function ensureReviewShellStyle() {
2153
2302
  height: max-content;
2154
2303
  min-width: 100%;
2155
2304
  min-height: 100%;
2156
- padding: 34px 40px 12px;
2305
+ padding: 34px 58px 12px 40px;
2157
2306
  }
2158
2307
 
2308
+ .df-review-target-stack {
2309
+ display: grid;
2310
+ justify-items: start;
2311
+ gap: 8px;
2312
+ width: max-content;
2313
+ max-width: 100%;
2314
+ }
2315
+
2159
2316
  .df-review-device {
2160
2317
  box-sizing: border-box;
2161
2318
  flex: 0 0 auto;
@@ -2178,93 +2335,267 @@ function ensureReviewShellStyle() {
2178
2335
  background: #fff;
2179
2336
  }
2180
2337
 
2181
- .df-review-device-frame {
2182
- position: relative;
2183
- box-sizing: border-box;
2184
- flex: 0 0 auto;
2338
+ .df-review-frame-link-stack {
2339
+ position: absolute;
2340
+ z-index: 14;
2341
+ top: 0;
2342
+ right: -44px;
2343
+ display: grid;
2344
+ gap: 8px;
2185
2345
  }
2186
2346
 
2187
- .df-review-ruler-corner {
2188
- position: absolute;
2189
- left: -26px;
2190
- top: -26px;
2191
- width: 26px;
2192
- height: 26px;
2193
- z-index: 6;
2194
- border-right: 1px solid var(--df-review-line-soft);
2195
- border-bottom: 1px solid var(--df-review-line-soft);
2196
- background: var(--df-review-color-ruler-surface);
2347
+ .df-review-frame-link {
2348
+ display: grid;
2349
+ place-items: center;
2350
+ width: 34px;
2351
+ height: 34px;
2352
+ border: 1px solid rgba(15, 23, 42, 0.16);
2353
+ border-radius: var(--df-review-radius-md);
2354
+ color: #17202c;
2355
+ background: rgba(255, 255, 255, 0.92);
2356
+ box-shadow: 0 10px 26px rgba(15, 23, 42, 0.18);
2357
+ text-decoration: none;
2358
+ backdrop-filter: blur(8px);
2359
+ transition: transform 140ms ease, border-color 140ms ease, color 140ms ease,
2360
+ background 140ms ease;
2197
2361
  }
2198
2362
 
2199
- .df-review-ruler-gutter {
2200
- position: absolute;
2201
- z-index: 6;
2202
- background: var(--df-review-color-ruler-surface);
2203
- color: var(--df-review-muted);
2204
- user-select: none;
2363
+ .df-review-frame-link:hover {
2364
+ transform: translateY(-1px);
2365
+ border-color: rgba(0, 102, 255, 0.42);
2366
+ color: #005be8;
2367
+ background: rgba(255, 255, 255, 0.98);
2205
2368
  }
2206
2369
 
2207
- .df-review-ruler-gutter.is-x {
2208
- left: 0;
2209
- right: 0;
2210
- top: -26px;
2211
- height: 26px;
2212
- border-bottom: 1px solid var(--df-review-line-soft);
2213
- background-image:
2214
- linear-gradient(
2215
- to right,
2216
- var(--df-review-color-ruler-tick-major) 1px,
2217
- transparent 1px
2218
- ),
2219
- linear-gradient(
2220
- to right,
2221
- var(--df-review-color-ruler-tick-minor) 1px,
2222
- transparent 1px
2223
- );
2224
- background-size:
2225
- calc(var(--df-review-ruler-step-x) * 5) 11px,
2226
- var(--df-review-ruler-step-x) 6px;
2227
- background-position: left bottom;
2228
- background-repeat: repeat-x;
2370
+ .df-review-frame-link svg {
2371
+ width: 18px;
2372
+ height: 18px;
2229
2373
  }
2230
2374
 
2231
- .df-review-ruler-gutter.is-y {
2232
- left: -26px;
2233
- top: 0;
2234
- bottom: 0;
2235
- width: 26px;
2236
- border-right: 1px solid var(--df-review-line-soft);
2237
- background-image:
2238
- linear-gradient(
2239
- to bottom,
2240
- var(--df-review-color-ruler-tick-major) 1px,
2241
- transparent 1px
2242
- ),
2243
- linear-gradient(
2244
- to bottom,
2245
- var(--df-review-color-ruler-tick-minor) 1px,
2246
- transparent 1px
2247
- );
2248
- background-size:
2249
- 11px calc(var(--df-review-ruler-step-y) * 5),
2250
- 6px var(--df-review-ruler-step-y);
2251
- background-position: right top;
2252
- background-repeat: repeat-y;
2375
+ .df-review-frame-link.is-target svg {
2376
+ fill: none;
2377
+ stroke: currentColor;
2378
+ stroke-width: 2;
2253
2379
  }
2254
2380
 
2255
- .df-review-ruler-frame-label {
2256
- position: absolute;
2257
- right: 6px;
2258
- top: 50%;
2259
- transform: translateY(-50%);
2260
- display: flex;
2261
- align-items: center;
2262
- gap: 6px;
2263
- padding: 4px 8px;
2264
- border: 1px solid var(--df-review-color-ruler-popover-border);
2265
- border-radius: var(--df-review-radius-sm);
2266
- background: var(--df-review-color-ruler-label);
2267
- line-height: 1;
2381
+ .df-review-frame-link.is-figma svg {
2382
+ fill: currentColor;
2383
+ stroke: none;
2384
+ }
2385
+
2386
+ .df-review-source-outline {
2387
+ position: fixed;
2388
+ z-index: 880;
2389
+ pointer-events: none;
2390
+ border: 2px solid rgba(124, 199, 255, 0.96);
2391
+ border-radius: 4px;
2392
+ box-shadow:
2393
+ 0 0 0 1px rgba(15, 18, 24, 0.58),
2394
+ 0 0 0 5px rgba(124, 199, 255, 0.16);
2395
+ }
2396
+
2397
+ .df-review-source-outline.is-pinned {
2398
+ border-color: var(--df-review-note);
2399
+ box-shadow:
2400
+ 0 0 0 1px rgba(15, 18, 24, 0.58),
2401
+ 0 0 0 5px rgba(243, 183, 95, 0.16);
2402
+ }
2403
+
2404
+ .df-review-source-popover {
2405
+ --df-review-source-popover-line: rgba(226, 233, 245, 0.16);
2406
+ --df-review-source-popover-text: #edf3fb;
2407
+ --df-review-source-popover-muted: rgba(237, 243, 251, 0.68);
2408
+ --df-review-source-popover-subtle: rgba(237, 243, 251, 0.5);
2409
+ --df-review-source-popover-hover: rgba(124, 199, 255, 0.14);
2410
+ position: fixed;
2411
+ z-index: 890;
2412
+ display: grid;
2413
+ width: fit-content;
2414
+ min-width: min(240px, calc(100vw - 24px));
2415
+ max-width: min(440px, calc(100vw - 24px));
2416
+ max-height: 260px;
2417
+ overflow: hidden;
2418
+ border: 1px solid var(--df-review-source-popover-line);
2419
+ border-radius: var(--df-review-radius-md);
2420
+ padding: 8px 6px 6px;
2421
+ color: var(--df-review-source-popover-text);
2422
+ color-scheme: dark;
2423
+ background: rgba(19, 24, 33, 0.96);
2424
+ box-shadow: var(--df-review-shadow-panel);
2425
+ backdrop-filter: blur(10px);
2426
+ }
2427
+
2428
+ .df-review-source-popover-close {
2429
+ position: absolute;
2430
+ top: 5px;
2431
+ right: 5px;
2432
+ z-index: 1;
2433
+ }
2434
+
2435
+ .df-review-source-popover-close button {
2436
+ display: grid;
2437
+ place-items: center;
2438
+ width: 24px;
2439
+ height: 24px;
2440
+ border: 1px solid transparent;
2441
+ border-radius: var(--df-review-radius-sm);
2442
+ padding: 0;
2443
+ color: var(--df-review-source-popover-subtle);
2444
+ background: transparent;
2445
+ font-size: 16px;
2446
+ font-weight: 800;
2447
+ line-height: 1;
2448
+ }
2449
+
2450
+ .df-review-source-popover-close button:hover {
2451
+ border-color: var(--df-review-source-popover-line);
2452
+ color: var(--df-review-source-popover-text);
2453
+ background: var(--df-review-source-popover-hover);
2454
+ }
2455
+
2456
+ .df-review-source-candidate-list {
2457
+ display: grid;
2458
+ gap: 0;
2459
+ min-height: 0;
2460
+ overflow: auto;
2461
+ }
2462
+
2463
+ .df-review-source-candidate {
2464
+ display: grid;
2465
+ width: 100%;
2466
+ min-height: 54px;
2467
+ border: 0;
2468
+ border-radius: var(--df-review-radius-sm);
2469
+ padding: 6px 30px 6px 8px;
2470
+ color: var(--df-review-source-popover-text);
2471
+ background: transparent;
2472
+ text-align: left;
2473
+ }
2474
+
2475
+ .df-review-source-candidate:hover {
2476
+ background: var(--df-review-source-popover-hover);
2477
+ }
2478
+
2479
+ .df-review-source-candidate-main {
2480
+ display: grid;
2481
+ gap: 2px;
2482
+ min-width: 0;
2483
+ }
2484
+
2485
+ .df-review-source-candidate-main strong,
2486
+ .df-review-source-candidate-main small {
2487
+ overflow: hidden;
2488
+ text-overflow: ellipsis;
2489
+ white-space: nowrap;
2490
+ }
2491
+
2492
+ .df-review-source-candidate-main strong {
2493
+ font-size: var(--df-review-font-size-xs);
2494
+ font-weight: 900;
2495
+ }
2496
+
2497
+ .df-review-source-candidate-main span {
2498
+ overflow-wrap: anywhere;
2499
+ color: var(--df-review-source-popover-muted);
2500
+ font-family: var(--df-review-font-mono);
2501
+ font-size: var(--df-review-font-size-2xs);
2502
+ line-height: 1.25;
2503
+ white-space: normal;
2504
+ }
2505
+
2506
+ .df-review-source-candidate-main small {
2507
+ color: var(--df-review-source-popover-subtle);
2508
+ font-family: var(--df-review-font-mono);
2509
+ font-size: var(--df-review-font-size-2xs);
2510
+ }
2511
+
2512
+ .df-review-device-frame {
2513
+ position: relative;
2514
+ box-sizing: border-box;
2515
+ flex: 0 0 auto;
2516
+ }
2517
+
2518
+ .df-review-ruler-corner {
2519
+ position: absolute;
2520
+ left: -26px;
2521
+ top: -26px;
2522
+ width: 26px;
2523
+ height: 26px;
2524
+ z-index: 6;
2525
+ border-right: 1px solid var(--df-review-line-soft);
2526
+ border-bottom: 1px solid var(--df-review-line-soft);
2527
+ background: var(--df-review-color-ruler-surface);
2528
+ }
2529
+
2530
+ .df-review-ruler-gutter {
2531
+ position: absolute;
2532
+ z-index: 6;
2533
+ background: var(--df-review-color-ruler-surface);
2534
+ color: var(--df-review-muted);
2535
+ user-select: none;
2536
+ }
2537
+
2538
+ .df-review-ruler-gutter.is-x {
2539
+ left: 0;
2540
+ right: 0;
2541
+ top: -26px;
2542
+ height: 26px;
2543
+ border-bottom: 1px solid var(--df-review-line-soft);
2544
+ background-image:
2545
+ linear-gradient(
2546
+ to right,
2547
+ var(--df-review-color-ruler-tick-major) 1px,
2548
+ transparent 1px
2549
+ ),
2550
+ linear-gradient(
2551
+ to right,
2552
+ var(--df-review-color-ruler-tick-minor) 1px,
2553
+ transparent 1px
2554
+ );
2555
+ background-size:
2556
+ calc(var(--df-review-ruler-step-x) * 5) 11px,
2557
+ var(--df-review-ruler-step-x) 6px;
2558
+ background-position: left bottom;
2559
+ background-repeat: repeat-x;
2560
+ }
2561
+
2562
+ .df-review-ruler-gutter.is-y {
2563
+ left: -26px;
2564
+ top: 0;
2565
+ bottom: 0;
2566
+ width: 26px;
2567
+ border-right: 1px solid var(--df-review-line-soft);
2568
+ background-image:
2569
+ linear-gradient(
2570
+ to bottom,
2571
+ var(--df-review-color-ruler-tick-major) 1px,
2572
+ transparent 1px
2573
+ ),
2574
+ linear-gradient(
2575
+ to bottom,
2576
+ var(--df-review-color-ruler-tick-minor) 1px,
2577
+ transparent 1px
2578
+ );
2579
+ background-size:
2580
+ 11px calc(var(--df-review-ruler-step-y) * 5),
2581
+ 6px var(--df-review-ruler-step-y);
2582
+ background-position: right top;
2583
+ background-repeat: repeat-y;
2584
+ }
2585
+
2586
+ .df-review-ruler-frame-label {
2587
+ position: absolute;
2588
+ right: 6px;
2589
+ top: 50%;
2590
+ transform: translateY(-50%);
2591
+ display: flex;
2592
+ align-items: center;
2593
+ gap: 6px;
2594
+ padding: 4px 8px;
2595
+ border: 1px solid var(--df-review-color-ruler-popover-border);
2596
+ border-radius: var(--df-review-radius-sm);
2597
+ background: var(--df-review-color-ruler-label);
2598
+ line-height: 1;
2268
2599
  white-space: nowrap;
2269
2600
  box-shadow: 0 6px 18px var(--df-review-color-ruler-popover-shadow);
2270
2601
  }
@@ -2383,7 +2714,7 @@ function ensureReviewShellStyle() {
2383
2714
  .df-review-shell,
2384
2715
  .df-review-shell.is-list-visible {
2385
2716
  grid-template-columns: minmax(0, 1fr) 0 32px;
2386
- grid-template-rows: auto minmax(0, 1fr);
2717
+ grid-template-rows: auto auto minmax(0, 1fr);
2387
2718
  }
2388
2719
 
2389
2720
  .df-review-shell.is-list-visible {
@@ -2439,8 +2770,9 @@ function ensureReviewShellStyle() {
2439
2770
  import {
2440
2771
  useCallback as useCallback11,
2441
2772
  useEffect as useEffect10,
2773
+ useMemo as useMemo7,
2442
2774
  useRef as useRef4,
2443
- useState as useState8
2775
+ useState as useState10
2444
2776
  } from "react";
2445
2777
 
2446
2778
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/createLucideIcon.mjs
@@ -2547,31 +2879,42 @@ var createLucideIcon = (iconName, iconNode) => {
2547
2879
  return Component;
2548
2880
  };
2549
2881
 
2550
- // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/circle-question-mark.mjs
2882
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/bot.mjs
2551
2883
  var __iconNode = [
2884
+ ["path", { d: "M12 8V4H8", key: "hb8ula" }],
2885
+ ["rect", { width: "16", height: "12", x: "4", y: "8", rx: "2", key: "enze0r" }],
2886
+ ["path", { d: "M2 14h2", key: "vft8re" }],
2887
+ ["path", { d: "M20 14h2", key: "4cs60a" }],
2888
+ ["path", { d: "M15 13v2", key: "1xurst" }],
2889
+ ["path", { d: "M9 13v2", key: "rq6x2g" }]
2890
+ ];
2891
+ var Bot = createLucideIcon("bot", __iconNode);
2892
+
2893
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/circle-question-mark.mjs
2894
+ var __iconNode2 = [
2552
2895
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
2553
2896
  ["path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3", key: "1u773s" }],
2554
2897
  ["path", { d: "M12 17h.01", key: "p32p05" }]
2555
2898
  ];
2556
- var CircleQuestionMark = createLucideIcon("circle-question-mark", __iconNode);
2899
+ var CircleQuestionMark = createLucideIcon("circle-question-mark", __iconNode2);
2557
2900
 
2558
2901
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/copy.mjs
2559
- var __iconNode2 = [
2902
+ var __iconNode3 = [
2560
2903
  ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
2561
2904
  ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
2562
2905
  ];
2563
- var Copy = createLucideIcon("copy", __iconNode2);
2906
+ var Copy = createLucideIcon("copy", __iconNode3);
2564
2907
 
2565
2908
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/external-link.mjs
2566
- var __iconNode3 = [
2909
+ var __iconNode4 = [
2567
2910
  ["path", { d: "M15 3h6v6", key: "1q9fwt" }],
2568
2911
  ["path", { d: "M10 14 21 3", key: "gplh6r" }],
2569
2912
  ["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6", key: "a6xqqp" }]
2570
2913
  ];
2571
- var ExternalLink = createLucideIcon("external-link", __iconNode3);
2914
+ var ExternalLink = createLucideIcon("external-link", __iconNode4);
2572
2915
 
2573
2916
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/eye-off.mjs
2574
- var __iconNode4 = [
2917
+ var __iconNode5 = [
2575
2918
  [
2576
2919
  "path",
2577
2920
  {
@@ -2589,10 +2932,10 @@ var __iconNode4 = [
2589
2932
  ],
2590
2933
  ["path", { d: "m2 2 20 20", key: "1ooewy" }]
2591
2934
  ];
2592
- var EyeOff = createLucideIcon("eye-off", __iconNode4);
2935
+ var EyeOff = createLucideIcon("eye-off", __iconNode5);
2593
2936
 
2594
2937
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/eye.mjs
2595
- var __iconNode5 = [
2938
+ var __iconNode6 = [
2596
2939
  [
2597
2940
  "path",
2598
2941
  {
@@ -2602,22 +2945,7 @@ var __iconNode5 = [
2602
2945
  ],
2603
2946
  ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
2604
2947
  ];
2605
- var Eye = createLucideIcon("eye", __iconNode5);
2606
-
2607
- // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/file-code-corner.mjs
2608
- var __iconNode6 = [
2609
- [
2610
- "path",
2611
- {
2612
- d: "M4 12.15V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-3.35",
2613
- key: "1wthlu"
2614
- }
2615
- ],
2616
- ["path", { d: "M14 2v5a1 1 0 0 0 1 1h5", key: "wfsgrz" }],
2617
- ["path", { d: "m5 16-3 3 3 3", key: "331omg" }],
2618
- ["path", { d: "m9 22 3-3-3-3", key: "lsp7cz" }]
2619
- ];
2620
- var FileCodeCorner = createLucideIcon("file-code-corner", __iconNode6);
2948
+ var Eye = createLucideIcon("eye", __iconNode6);
2621
2949
 
2622
2950
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/grip-vertical.mjs
2623
2951
  var __iconNode7 = [
@@ -2647,16 +2975,24 @@ var __iconNode9 = [
2647
2975
  ];
2648
2976
  var LayoutGrid = createLucideIcon("layout-grid", __iconNode9);
2649
2977
 
2650
- // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/list-filter.mjs
2978
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/link-2.mjs
2651
2979
  var __iconNode10 = [
2980
+ ["path", { d: "M9 17H7A5 5 0 0 1 7 7h2", key: "8i5ue5" }],
2981
+ ["path", { d: "M15 7h2a5 5 0 1 1 0 10h-2", key: "1b9ql8" }],
2982
+ ["line", { x1: "8", x2: "16", y1: "12", y2: "12", key: "1jonct" }]
2983
+ ];
2984
+ var Link2 = createLucideIcon("link-2", __iconNode10);
2985
+
2986
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/list-filter.mjs
2987
+ var __iconNode11 = [
2652
2988
  ["path", { d: "M2 5h20", key: "1fs1ex" }],
2653
2989
  ["path", { d: "M6 12h12", key: "8npq4p" }],
2654
2990
  ["path", { d: "M9 19h6", key: "456am0" }]
2655
2991
  ];
2656
- var ListFilter = createLucideIcon("list-filter", __iconNode10);
2992
+ var ListFilter = createLucideIcon("list-filter", __iconNode11);
2657
2993
 
2658
2994
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/map.mjs
2659
- var __iconNode11 = [
2995
+ var __iconNode12 = [
2660
2996
  [
2661
2997
  "path",
2662
2998
  {
@@ -2667,27 +3003,27 @@ var __iconNode11 = [
2667
3003
  ["path", { d: "M15 5.764v15", key: "1pn4in" }],
2668
3004
  ["path", { d: "M9 3.236v15", key: "1uimfh" }]
2669
3005
  ];
2670
- var Map2 = createLucideIcon("map", __iconNode11);
3006
+ var Map2 = createLucideIcon("map", __iconNode12);
2671
3007
 
2672
3008
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/maximize-2.mjs
2673
- var __iconNode12 = [
3009
+ var __iconNode13 = [
2674
3010
  ["path", { d: "M15 3h6v6", key: "1q9fwt" }],
2675
3011
  ["path", { d: "m21 3-7 7", key: "1l2asr" }],
2676
3012
  ["path", { d: "m3 21 7-7", key: "tjx5ai" }],
2677
3013
  ["path", { d: "M9 21H3v-6", key: "wtvkvv" }]
2678
3014
  ];
2679
- var Maximize2 = createLucideIcon("maximize-2", __iconNode12);
3015
+ var Maximize2 = createLucideIcon("maximize-2", __iconNode13);
2680
3016
 
2681
3017
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/monitor.mjs
2682
- var __iconNode13 = [
3018
+ var __iconNode14 = [
2683
3019
  ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2", key: "48i651" }],
2684
3020
  ["line", { x1: "8", x2: "16", y1: "21", y2: "21", key: "1svkeh" }],
2685
3021
  ["line", { x1: "12", x2: "12", y1: "17", y2: "21", key: "vw1qmm" }]
2686
3022
  ];
2687
- var Monitor = createLucideIcon("monitor", __iconNode13);
3023
+ var Monitor = createLucideIcon("monitor", __iconNode14);
2688
3024
 
2689
3025
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/pencil.mjs
2690
- var __iconNode14 = [
3026
+ var __iconNode15 = [
2691
3027
  [
2692
3028
  "path",
2693
3029
  {
@@ -2697,25 +3033,25 @@ var __iconNode14 = [
2697
3033
  ],
2698
3034
  ["path", { d: "m15 5 4 4", key: "1mk7zo" }]
2699
3035
  ];
2700
- var Pencil = createLucideIcon("pencil", __iconNode14);
3036
+ var Pencil = createLucideIcon("pencil", __iconNode15);
2701
3037
 
2702
3038
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/rectangle-horizontal.mjs
2703
- var __iconNode15 = [
3039
+ var __iconNode16 = [
2704
3040
  ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2", key: "9lu3g6" }]
2705
3041
  ];
2706
- var RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode15);
3042
+ var RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode16);
2707
3043
 
2708
3044
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/refresh-cw.mjs
2709
- var __iconNode16 = [
3045
+ var __iconNode17 = [
2710
3046
  ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }],
2711
3047
  ["path", { d: "M21 3v5h-5", key: "1q7to0" }],
2712
3048
  ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }],
2713
3049
  ["path", { d: "M8 16H3v5", key: "1cv678" }]
2714
3050
  ];
2715
- var RefreshCw = createLucideIcon("refresh-cw", __iconNode16);
3051
+ var RefreshCw = createLucideIcon("refresh-cw", __iconNode17);
2716
3052
 
2717
3053
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/ruler.mjs
2718
- var __iconNode17 = [
3054
+ var __iconNode18 = [
2719
3055
  [
2720
3056
  "path",
2721
3057
  {
@@ -2728,19 +3064,19 @@ var __iconNode17 = [
2728
3064
  ["path", { d: "m8.5 6.5 2-2", key: "vc6u1g" }],
2729
3065
  ["path", { d: "m17.5 15.5 2-2", key: "wo5hmg" }]
2730
3066
  ];
2731
- var Ruler = createLucideIcon("ruler", __iconNode17);
3067
+ var Ruler = createLucideIcon("ruler", __iconNode18);
2732
3068
 
2733
3069
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/scan.mjs
2734
- var __iconNode18 = [
3070
+ var __iconNode19 = [
2735
3071
  ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2", key: "aa7l1z" }],
2736
3072
  ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2", key: "4qcy5o" }],
2737
3073
  ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2", key: "6vwrx8" }],
2738
3074
  ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2", key: "ioqczr" }]
2739
3075
  ];
2740
- var Scan = createLucideIcon("scan", __iconNode18);
3076
+ var Scan = createLucideIcon("scan", __iconNode19);
2741
3077
 
2742
3078
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/settings.mjs
2743
- var __iconNode19 = [
3079
+ var __iconNode20 = [
2744
3080
  [
2745
3081
  "path",
2746
3082
  {
@@ -2750,17 +3086,17 @@ var __iconNode19 = [
2750
3086
  ],
2751
3087
  ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
2752
3088
  ];
2753
- var Settings = createLucideIcon("settings", __iconNode19);
3089
+ var Settings = createLucideIcon("settings", __iconNode20);
2754
3090
 
2755
3091
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/smartphone.mjs
2756
- var __iconNode20 = [
3092
+ var __iconNode21 = [
2757
3093
  ["rect", { width: "14", height: "20", x: "5", y: "2", rx: "2", ry: "2", key: "1yt0o3" }],
2758
3094
  ["path", { d: "M12 18h.01", key: "mhygvu" }]
2759
3095
  ];
2760
- var Smartphone = createLucideIcon("smartphone", __iconNode20);
3096
+ var Smartphone = createLucideIcon("smartphone", __iconNode21);
2761
3097
 
2762
3098
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/square-mouse-pointer.mjs
2763
- var __iconNode21 = [
3099
+ var __iconNode22 = [
2764
3100
  [
2765
3101
  "path",
2766
3102
  {
@@ -2770,10 +3106,10 @@ var __iconNode21 = [
2770
3106
  ],
2771
3107
  ["path", { d: "M21 11V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6", key: "14rsvq" }]
2772
3108
  ];
2773
- var SquareMousePointer = createLucideIcon("square-mouse-pointer", __iconNode21);
3109
+ var SquareMousePointer = createLucideIcon("square-mouse-pointer", __iconNode22);
2774
3110
 
2775
3111
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/sticky-note.mjs
2776
- var __iconNode22 = [
3112
+ var __iconNode23 = [
2777
3113
  [
2778
3114
  "path",
2779
3115
  {
@@ -2783,24 +3119,15 @@ var __iconNode22 = [
2783
3119
  ],
2784
3120
  ["path", { d: "M15 3v5a1 1 0 0 0 1 1h5", key: "6s6qgf" }]
2785
3121
  ];
2786
- var StickyNote = createLucideIcon("sticky-note", __iconNode22);
3122
+ var StickyNote = createLucideIcon("sticky-note", __iconNode23);
2787
3123
 
2788
3124
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/upload.mjs
2789
- var __iconNode23 = [
3125
+ var __iconNode24 = [
2790
3126
  ["path", { d: "M12 3v12", key: "1x0j5s" }],
2791
3127
  ["path", { d: "m17 8-5-5-5 5", key: "7q97r8" }],
2792
3128
  ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }]
2793
3129
  ];
2794
- var Upload = createLucideIcon("upload", __iconNode23);
2795
-
2796
- // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/users.mjs
2797
- var __iconNode24 = [
2798
- ["path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2", key: "1yyitq" }],
2799
- ["path", { d: "M16 3.128a4 4 0 0 1 0 7.744", key: "16gr8j" }],
2800
- ["path", { d: "M22 21v-2a4 4 0 0 0-3-3.87", key: "kshegd" }],
2801
- ["circle", { cx: "9", cy: "7", r: "4", key: "nufk8" }]
2802
- ];
2803
- var Users = createLucideIcon("users", __iconNode24);
3130
+ var Upload = createLucideIcon("upload", __iconNode24);
2804
3131
 
2805
3132
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/x.mjs
2806
3133
  var __iconNode25 = [
@@ -2844,6 +3171,45 @@ var normalizeTarget = (value, reviewPathPrefix = DEFAULT_REVIEW_PATH_PREFIX) =>
2844
3171
  const reviewPrefix = normalizeReviewPathPrefix(reviewPathPrefix);
2845
3172
  return normalized === reviewPrefix || normalized.startsWith(`${reviewPrefix}/`) ? "/" : normalized;
2846
3173
  };
3174
+ var parseReviewAddressInput = (value, reviewPathPrefix = DEFAULT_REVIEW_PATH_PREFIX) => {
3175
+ const raw = value.trim();
3176
+ if (!raw) return { target: "/" };
3177
+ const parsedUrl = parseSameOriginUrl(raw);
3178
+ if (!parsedUrl) {
3179
+ return { target: normalizeTarget(raw, reviewPathPrefix) };
3180
+ }
3181
+ const reviewPrefix = normalizeReviewPathPrefix(reviewPathPrefix);
3182
+ const isReviewUrl = parsedUrl.pathname === reviewPrefix || parsedUrl.pathname.startsWith(`${reviewPrefix}/`);
3183
+ if (!isReviewUrl) {
3184
+ return {
3185
+ target: normalizeTarget(parsedUrl.pathname, reviewPathPrefix)
3186
+ };
3187
+ }
3188
+ const source = parsedUrl.searchParams.get("source")?.trim();
3189
+ return {
3190
+ height: getPositiveParamNumber(parsedUrl.searchParams, "h"),
3191
+ itemId: parsedUrl.searchParams.get("item"),
3192
+ source: source ? source : void 0,
3193
+ target: normalizeTarget(
3194
+ parsedUrl.searchParams.get("target") ?? "/",
3195
+ reviewPathPrefix
3196
+ ),
3197
+ width: getPositiveParamNumber(parsedUrl.searchParams, "w")
3198
+ };
3199
+ };
3200
+ function parseSameOriginUrl(value) {
3201
+ if (typeof window === "undefined") return null;
3202
+ try {
3203
+ const url = new URL(value, window.location.origin);
3204
+ return url.origin === window.location.origin ? url : null;
3205
+ } catch {
3206
+ return null;
3207
+ }
3208
+ }
3209
+ function getPositiveParamNumber(params, name) {
3210
+ const value = Number(params.get(name));
3211
+ return Number.isFinite(value) && value > 0 ? value : void 0;
3212
+ }
2847
3213
  var getInitialTarget = (reviewPathPrefix = DEFAULT_REVIEW_PATH_PREFIX) => {
2848
3214
  if (typeof window === "undefined") return "/";
2849
3215
  const target = new URLSearchParams(window.location.search).get("target");
@@ -2884,6 +3250,10 @@ var updateShellUrl = (target, size, source) => {
2884
3250
  window.history.replaceState(null, "", `${url.pathname}${url.search}`);
2885
3251
  };
2886
3252
  var updateShellUrlForItem = (target, size, itemId, source) => {
3253
+ const url = getShellUrlForItem(target, size, itemId, source);
3254
+ window.history.replaceState(null, "", `${url.pathname}${url.search}`);
3255
+ };
3256
+ var getShellUrlForItem = (target, size, itemId, source) => {
2887
3257
  const url = new URL(window.location.href);
2888
3258
  url.searchParams.set("target", target);
2889
3259
  url.searchParams.set("w", String(size.width));
@@ -2894,7 +3264,7 @@ var updateShellUrlForItem = (target, size, itemId, source) => {
2894
3264
  } else {
2895
3265
  url.searchParams.delete("source");
2896
3266
  }
2897
- window.history.replaceState(null, "", `${url.pathname}${url.search}`);
3267
+ return url;
2898
3268
  };
2899
3269
  var getInitialItemId = () => {
2900
3270
  if (typeof window === "undefined") return null;
@@ -2976,7 +3346,9 @@ var toReviewViewportPresets = (presets) => presets.map((preset) => ({
2976
3346
  label: preset.label,
2977
3347
  width: preset.width,
2978
3348
  height: preset.height,
2979
- scope: getViewportPresetKind(preset)
3349
+ scope: getViewportPresetKind(preset),
3350
+ designWidth: preset.designWidth,
3351
+ designHeight: preset.designHeight
2980
3352
  }));
2981
3353
  var getIsFigmaOverlayAvailable = (preset) => {
2982
3354
  const kind = getViewportPresetKind(preset);
@@ -3450,11 +3822,40 @@ var ReviewSettingsModal = ({
3450
3822
  );
3451
3823
  };
3452
3824
 
3825
+ // src/react-shell/sitemap/modal.tsx
3826
+ import {
3827
+ useMemo as useMemo2,
3828
+ useState
3829
+ } from "react";
3830
+
3453
3831
  // src/react-shell/sitemap/tree.ts
3454
- var EMPTY_SITEMAP_QA_COUNT = {
3832
+ var WORKFLOW_STATUSES = [
3833
+ "todo",
3834
+ "doing",
3835
+ "review",
3836
+ "hold",
3837
+ "done"
3838
+ ];
3839
+ var createEmptySitemapQaCount = () => ({
3840
+ total: 0,
3841
+ remaining: 0,
3455
3842
  local: 0,
3456
- remote: 0
3457
- };
3843
+ remote: 0,
3844
+ status: {
3845
+ todo: 0,
3846
+ doing: 0,
3847
+ review: 0,
3848
+ hold: 0,
3849
+ done: 0
3850
+ },
3851
+ scope: {},
3852
+ viewport: {}
3853
+ });
3854
+ var createSitemapViewportColumn = (preset, index) => ({
3855
+ key: `${index}:${preset.width}x${preset.height}`,
3856
+ label: preset.label,
3857
+ title: `${preset.label} ${preset.width}x${preset.height}`
3858
+ });
3458
3859
  var normalizeSitemapHref = (href) => {
3459
3860
  const [path = "/"] = href.split(/[?#]/);
3460
3861
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
@@ -3479,10 +3880,45 @@ var mergeSitemapUsers = (users) => {
3479
3880
  return Array.from(userByKey.values());
3480
3881
  };
3481
3882
  var addSitemapQaCounts = (first, second) => ({
3883
+ total: first.total + second.total,
3884
+ remaining: first.remaining + second.remaining,
3482
3885
  local: first.local + second.local,
3483
- remote: first.remote + second.remote
3886
+ remote: first.remote + second.remote,
3887
+ status: WORKFLOW_STATUSES.reduce(
3888
+ (statusCounts, status) => ({
3889
+ ...statusCounts,
3890
+ [status]: first.status[status] + second.status[status]
3891
+ }),
3892
+ {}
3893
+ ),
3894
+ scope: Array.from(
3895
+ /* @__PURE__ */ new Set([
3896
+ ...Object.keys(first.scope),
3897
+ ...Object.keys(second.scope)
3898
+ ])
3899
+ ).reduce(
3900
+ (scopeCounts, scope) => ({
3901
+ ...scopeCounts,
3902
+ [scope]: (first.scope[scope] ?? 0) + (second.scope[scope] ?? 0)
3903
+ }),
3904
+ {}
3905
+ ),
3906
+ viewport: Array.from(
3907
+ /* @__PURE__ */ new Set([...Object.keys(first.viewport), ...Object.keys(second.viewport)])
3908
+ ).reduce(
3909
+ (viewportCounts, viewportKey) => ({
3910
+ ...viewportCounts,
3911
+ [viewportKey]: {
3912
+ total: (first.viewport[viewportKey]?.total ?? 0) + (second.viewport[viewportKey]?.total ?? 0),
3913
+ remaining: (first.viewport[viewportKey]?.remaining ?? 0) + (second.viewport[viewportKey]?.remaining ?? 0)
3914
+ }
3915
+ }),
3916
+ {}
3917
+ )
3484
3918
  });
3485
- var createSitemapRows = (pages, activeRoute, pageQaCounts, pagePresenceUsers, getPageTarget) => {
3919
+ var createSitemapRows = (pages, activeRoute, pageQaCounts, pagePresenceUsers, getPageTarget, options = {}) => {
3920
+ const sortKey = options.sortKey ?? "page";
3921
+ const sortDirection = options.sortDirection ?? "asc";
3486
3922
  const root = createSitemapNode("/", "/", false);
3487
3923
  pages.forEach((page) => {
3488
3924
  const pageHref = page.href.startsWith("/") ? page.href : `/${page.href}`;
@@ -3509,59 +3945,88 @@ var createSitemapRows = (pages, activeRoute, pageQaCounts, pagePresenceUsers, ge
3509
3945
  });
3510
3946
  });
3511
3947
  const getDirectCount = (node) => {
3512
- if (!node.isPage) return EMPTY_SITEMAP_QA_COUNT;
3513
- return pageQaCounts.get(getPageTarget(node.href)) ?? EMPTY_SITEMAP_QA_COUNT;
3948
+ if (!node.isPage) return createEmptySitemapQaCount();
3949
+ return pageQaCounts.get(getPageTarget(node.href)) ?? createEmptySitemapQaCount();
3514
3950
  };
3515
3951
  const getDirectUsers = (node) => {
3516
3952
  if (!node.isPage) return [];
3517
3953
  return pagePresenceUsers.get(getPageTarget(node.href)) ?? [];
3518
3954
  };
3519
- const rows = [];
3520
- const appendNodeRows = (node, depth, ancestorLastList, isLastNode) => {
3521
- const children = Array.from(node.children.values());
3955
+ const createNodeSummary = (node) => {
3522
3956
  const directCount = getDirectCount(node);
3523
3957
  const directUsers = getDirectUsers(node);
3524
- let rowIndex = null;
3958
+ const children = Array.from(node.children.values()).map(createNodeSummary);
3959
+ const childAggregate = children.reduce(
3960
+ (aggregate, child) => ({
3961
+ count: addSitemapQaCounts(aggregate.count, child.count),
3962
+ users: mergeSitemapUsers([...aggregate.users, ...child.users])
3963
+ }),
3964
+ {
3965
+ count: createEmptySitemapQaCount(),
3966
+ users: []
3967
+ }
3968
+ );
3969
+ return {
3970
+ node,
3971
+ directCount,
3972
+ directUsers,
3973
+ count: node.isPage ? addSitemapQaCounts(directCount, childAggregate.count) : childAggregate.count,
3974
+ users: mergeSitemapUsers([...directUsers, ...childAggregate.users]),
3975
+ children
3976
+ };
3977
+ };
3978
+ const getSortValue = (summary) => {
3979
+ if (sortKey === "page") return summary.node.label;
3980
+ if (sortKey === "total") return summary.count.remaining;
3981
+ if (sortKey === "review") return summary.count.status.review;
3982
+ if (sortKey === "hold") return summary.count.status.hold;
3983
+ if (sortKey === "online") return summary.users.length;
3984
+ if (sortKey.startsWith("viewport:")) {
3985
+ const viewportKey = sortKey.slice("viewport:".length);
3986
+ return summary.count.viewport[viewportKey]?.remaining ?? 0;
3987
+ }
3988
+ return 0;
3989
+ };
3990
+ const sortSummaries = (summaries) => {
3991
+ return [...summaries].sort((a, b) => {
3992
+ const firstValue = getSortValue(a);
3993
+ const secondValue = getSortValue(b);
3994
+ const valueDiff = typeof firstValue === "string" && typeof secondValue === "string" ? firstValue.localeCompare(secondValue) : Number(firstValue) - Number(secondValue);
3995
+ if (valueDiff !== 0) {
3996
+ return sortDirection === "asc" ? valueDiff : -valueDiff;
3997
+ }
3998
+ const totalDiff = b.count.remaining - a.count.remaining;
3999
+ if (totalDiff !== 0) return totalDiff;
4000
+ return a.node.label.localeCompare(b.node.label);
4001
+ });
4002
+ };
4003
+ const rows = [];
4004
+ const appendSummaryRows = (summary, depth, ancestorLastList, isLastNode) => {
4005
+ const { node } = summary;
4006
+ const rowCount = node.isPage ? summary.directCount : summary.count;
4007
+ const rowUsers = node.isPage ? summary.directUsers : summary.users;
3525
4008
  if (node.isPage || depth > 0) {
3526
4009
  const prefix = depth === 0 ? "" : `${ancestorLastList.map((isLast) => isLast ? " " : "\u2502 ").join("")}${isLastNode ? "\u2514\u2500 " : "\u251C\u2500 "}`;
3527
4010
  const pageTarget = node.isPage ? getPageTarget(node.href) : null;
3528
- rowIndex = rows.length;
3529
4011
  rows.push({
3530
4012
  href: node.href,
3531
4013
  label: node.label,
3532
4014
  prefix,
3533
4015
  isPage: node.isPage,
3534
4016
  isActive: pageTarget === activeRoute,
3535
- qaCount: directCount,
3536
- users: directUsers
4017
+ qaCount: rowCount,
4018
+ users: rowUsers
3537
4019
  });
3538
4020
  }
3539
- const childAggregate = children.reduce(
3540
- (aggregate, child, childIndex) => {
3541
- const childResult = appendNodeRows(
3542
- child,
3543
- depth + 1,
3544
- depth === 0 ? [] : [...ancestorLastList, isLastNode],
3545
- childIndex === children.length - 1
3546
- );
3547
- return {
3548
- count: addSitemapQaCounts(aggregate.count, childResult.count),
3549
- users: mergeSitemapUsers([...aggregate.users, ...childResult.users])
3550
- };
3551
- },
3552
- { count: EMPTY_SITEMAP_QA_COUNT, users: [] }
3553
- );
3554
- if (rowIndex !== null && !node.isPage) {
3555
- rows[rowIndex] = {
3556
- ...rows[rowIndex],
3557
- qaCount: childAggregate.count,
3558
- users: childAggregate.users
3559
- };
3560
- }
3561
- return {
3562
- count: node.isPage ? addSitemapQaCounts(directCount, childAggregate.count) : childAggregate.count,
3563
- users: mergeSitemapUsers([...directUsers, ...childAggregate.users])
3564
- };
4021
+ const visibleChildren = sortSummaries(summary.children);
4022
+ visibleChildren.forEach((child, childIndex) => {
4023
+ appendSummaryRows(
4024
+ child,
4025
+ depth + 1,
4026
+ depth === 0 ? [] : [...ancestorLastList, isLastNode],
4027
+ childIndex === visibleChildren.length - 1
4028
+ );
4029
+ });
3565
4030
  };
3566
4031
  if (root.isPage) {
3567
4032
  const directCount = getDirectCount(root);
@@ -3576,30 +4041,83 @@ var createSitemapRows = (pages, activeRoute, pageQaCounts, pagePresenceUsers, ge
3576
4041
  users: directUsers
3577
4042
  });
3578
4043
  }
3579
- Array.from(root.children.values()).forEach((node, index, siblings) => {
3580
- appendNodeRows(node, 1, [], index === siblings.length - 1);
4044
+ const rootSummaries = sortSummaries(
4045
+ Array.from(root.children.values()).map(createNodeSummary)
4046
+ );
4047
+ rootSummaries.forEach((summary, index, siblings) => {
4048
+ appendSummaryRows(summary, 1, [], index === siblings.length - 1);
3581
4049
  });
3582
4050
  return rows;
3583
4051
  };
3584
4052
 
3585
4053
  // src/react-shell/sitemap/modal.tsx
3586
4054
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
4055
+ var getNextSortDirection = (current, key) => {
4056
+ if (current.key !== key) return key === "page" ? "asc" : "desc";
4057
+ return current.direction === "desc" ? "asc" : "desc";
4058
+ };
4059
+ var getSortIndicator = (sort, key) => {
4060
+ if (sort.key !== key) return "";
4061
+ return sort.direction === "desc" ? "\u2193" : "\u2191";
4062
+ };
4063
+ var mergePresenceUsers = (users) => {
4064
+ const userByKey = /* @__PURE__ */ new Map();
4065
+ users.forEach((user) => {
4066
+ const key = user.sessionId || user.userId;
4067
+ const currentUser = userByKey.get(key);
4068
+ if (!currentUser || Date.parse(user.updatedAt) >= Date.parse(currentUser.updatedAt)) {
4069
+ userByKey.set(key, user);
4070
+ }
4071
+ });
4072
+ return Array.from(userByKey.values());
4073
+ };
3587
4074
  var SitemapModal = ({
3588
4075
  pages,
3589
4076
  activeRoute,
4077
+ allQaCount,
4078
+ isAllQaVisible,
3590
4079
  pageQaCounts,
3591
4080
  pagePresenceUsers,
3592
4081
  getPageTarget,
3593
4082
  onClose,
4083
+ onSelectAllQa,
3594
4084
  onSelectPage
3595
4085
  }) => {
4086
+ const [sort, setSort] = useState({
4087
+ key: "total",
4088
+ direction: "desc"
4089
+ });
4090
+ const allQaUsers = useMemo2(
4091
+ () => mergePresenceUsers(Array.from(pagePresenceUsers.values()).flat()),
4092
+ [pagePresenceUsers]
4093
+ );
3596
4094
  const sitemapRows = createSitemapRows(
3597
4095
  pages,
3598
4096
  activeRoute,
3599
4097
  pageQaCounts,
3600
4098
  pagePresenceUsers,
3601
- getPageTarget
4099
+ getPageTarget,
4100
+ {
4101
+ sortKey: sort.key,
4102
+ sortDirection: sort.direction
4103
+ }
3602
4104
  );
4105
+ const gridStyle = {
4106
+ "--df-review-sitemap-grid-template": "minmax(190px, 1fr) 74px 78px 64px minmax(108px, 160px)"
4107
+ };
4108
+ const sortHeaders = [
4109
+ { key: "page", label: "", title: "Page", className: "is-page" },
4110
+ { key: "total", label: "Total", title: "Remaining total" },
4111
+ { key: "review", label: "Review" },
4112
+ { key: "hold", label: "Hold" },
4113
+ { key: "online", label: "Online", className: "is-online" }
4114
+ ];
4115
+ const setSortKey = (key) => {
4116
+ setSort((current) => ({
4117
+ key,
4118
+ direction: getNextSortDirection(current, key)
4119
+ }));
4120
+ };
3603
4121
  return /* @__PURE__ */ jsxs3(
3604
4122
  "div",
3605
4123
  {
@@ -3623,48 +4141,65 @@ var SitemapModal = ({
3623
4141
  /* @__PURE__ */ jsx3("strong", { children: "Sitemap" }),
3624
4142
  /* @__PURE__ */ jsxs3("span", { children: [
3625
4143
  pages.length,
3626
- " pages"
4144
+ " pages \xB7 ",
4145
+ allQaCount.remaining,
4146
+ " remaining \xB7",
4147
+ " ",
4148
+ allQaCount.status.review,
4149
+ " review \xB7 ",
4150
+ allQaCount.status.hold,
4151
+ " hold"
3627
4152
  ] })
3628
4153
  ] }),
3629
4154
  /* @__PURE__ */ jsx3("button", { "aria-label": "Close sitemap", type: "button", onClick: onClose, children: "x" })
3630
4155
  ] }),
3631
- /* @__PURE__ */ jsxs3("div", { className: "df-review-sitemap-list", children: [
3632
- /* @__PURE__ */ jsxs3("div", { className: "df-review-sitemap-table-head", "aria-hidden": "true", children: [
3633
- /* @__PURE__ */ jsx3("span", { children: "Page" }),
3634
- /* @__PURE__ */ jsx3("span", { children: "Local" }),
3635
- /* @__PURE__ */ jsx3("span", { children: "Remote" }),
3636
- /* @__PURE__ */ jsx3("span", { children: "Online" })
3637
- ] }),
4156
+ /* @__PURE__ */ jsxs3("div", { className: "df-review-sitemap-list", style: gridStyle, children: [
4157
+ /* @__PURE__ */ jsx3("div", { className: "df-review-sitemap-table-head", role: "row", children: sortHeaders.map((header) => /* @__PURE__ */ jsxs3(
4158
+ "button",
4159
+ {
4160
+ "aria-label": `Sort sitemap by ${header.title ?? header.label}`,
4161
+ className: [
4162
+ "df-review-sitemap-sort",
4163
+ header.className ?? "",
4164
+ sort.key === header.key ? "is-active" : ""
4165
+ ].filter(Boolean).join(" "),
4166
+ title: header.title ?? header.label,
4167
+ type: "button",
4168
+ onClick: () => setSortKey(header.key),
4169
+ children: [
4170
+ /* @__PURE__ */ jsx3(
4171
+ "span",
4172
+ {
4173
+ "aria-hidden": "true",
4174
+ className: "df-review-sitemap-sort-indicator",
4175
+ children: getSortIndicator(sort, header.key)
4176
+ }
4177
+ ),
4178
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-sort-label", children: header.label })
4179
+ ]
4180
+ },
4181
+ header.key
4182
+ )) }),
3638
4183
  sitemapRows.map((row) => {
3639
4184
  const rowClassName = [
3640
4185
  "df-review-sitemap-row",
3641
4186
  row.isPage ? "is-page" : "is-folder",
3642
4187
  row.isActive ? "is-active" : ""
3643
4188
  ].filter(Boolean).join(" ");
3644
- const rowContent = /* @__PURE__ */ jsxs3(Fragment, { children: [
3645
- /* @__PURE__ */ jsxs3("span", { className: "df-review-sitemap-path", children: [
3646
- /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-tree-prefix", children: row.prefix }),
3647
- /* @__PURE__ */ jsx3("span", { children: row.label })
3648
- ] }),
3649
- /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-cell is-local", children: row.qaCount.local }),
3650
- /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-cell is-remote", children: row.qaCount.remote }),
3651
- /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-cell is-online", children: row.users.length > 0 ? /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-users", children: row.users.map((user) => /* @__PURE__ */ jsx3(
3652
- "span",
3653
- {
3654
- className: "df-review-sitemap-user",
3655
- style: {
3656
- "--df-review-presence-color": user.color
3657
- },
3658
- children: user.userId
3659
- },
3660
- user.sessionId
3661
- )) }) : /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-online-empty", children: "0" }) })
3662
- ] });
4189
+ const rowContent = /* @__PURE__ */ jsx3(
4190
+ SitemapRowContent,
4191
+ {
4192
+ label: row.label,
4193
+ prefix: row.prefix,
4194
+ qaCount: row.qaCount,
4195
+ users: row.users
4196
+ }
4197
+ );
3663
4198
  if (!row.isPage) {
3664
4199
  return /* @__PURE__ */ jsx3(
3665
4200
  "div",
3666
4201
  {
3667
- "aria-label": `${row.href} group / local ${row.qaCount.local} QA / remote ${row.qaCount.remote} QA / ${row.users.length} online`,
4202
+ "aria-label": `${row.href} group / ${row.qaCount.remaining} remaining / ${row.qaCount.status.review} review / ${row.qaCount.status.hold} hold / ${row.users.length} online`,
3668
4203
  className: rowClassName,
3669
4204
  role: "row",
3670
4205
  children: rowContent
@@ -3675,7 +4210,7 @@ var SitemapModal = ({
3675
4210
  return /* @__PURE__ */ jsx3(
3676
4211
  "button",
3677
4212
  {
3678
- "aria-label": `${row.href} / local ${row.qaCount.local} QA / remote ${row.qaCount.remote} QA / ${row.users.length} online`,
4213
+ "aria-label": `${row.href} / ${row.qaCount.remaining} remaining / ${row.qaCount.status.review} review / ${row.qaCount.status.hold} hold / ${row.users.length} online`,
3679
4214
  className: rowClassName,
3680
4215
  type: "button",
3681
4216
  onClick: () => onSelectPage(row.href),
@@ -3683,25 +4218,102 @@ var SitemapModal = ({
3683
4218
  },
3684
4219
  row.href
3685
4220
  );
3686
- })
4221
+ }),
4222
+ /* @__PURE__ */ jsx3(
4223
+ "button",
4224
+ {
4225
+ "aria-label": `All QA / ${allQaCount.remaining} remaining / ${allQaCount.status.review} review / ${allQaCount.status.hold} hold`,
4226
+ className: `df-review-sitemap-row is-summary${isAllQaVisible ? " is-active" : ""}`,
4227
+ type: "button",
4228
+ onClick: onSelectAllQa,
4229
+ children: /* @__PURE__ */ jsx3(
4230
+ SitemapRowContent,
4231
+ {
4232
+ label: "",
4233
+ prefix: "",
4234
+ qaCount: allQaCount,
4235
+ users: allQaUsers
4236
+ }
4237
+ )
4238
+ }
4239
+ )
3687
4240
  ] })
3688
4241
  ] })
3689
4242
  ]
3690
4243
  }
3691
4244
  );
3692
4245
  };
4246
+ var SitemapRowContent = ({
4247
+ label,
4248
+ prefix,
4249
+ qaCount,
4250
+ users
4251
+ }) => /* @__PURE__ */ jsxs3(Fragment, { children: [
4252
+ /* @__PURE__ */ jsxs3("span", { className: "df-review-sitemap-path", children: [
4253
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-tree-prefix", children: prefix }),
4254
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-label", children: label })
4255
+ ] }),
4256
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-cell is-total", children: /* @__PURE__ */ jsx3("strong", { children: qaCount.remaining }) }),
4257
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-cell is-review", children: qaCount.status.review }),
4258
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-cell is-hold", children: qaCount.status.hold }),
4259
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-cell is-online", children: users.length > 0 ? /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-users", children: users.map((user) => /* @__PURE__ */ jsx3(
4260
+ "span",
4261
+ {
4262
+ className: "df-review-sitemap-user",
4263
+ style: {
4264
+ "--df-review-presence-color": user.color
4265
+ },
4266
+ children: user.userId
4267
+ },
4268
+ user.sessionId
4269
+ )) }) : null })
4270
+ ] });
4271
+
4272
+ // src/react-shell/figma.ts
4273
+ function getTargetFigmaFrameConfig(targetWindow) {
4274
+ try {
4275
+ const config = targetWindow?.__figma;
4276
+ if (!config || typeof config !== "object") return null;
4277
+ const desktopNodeId = normalizeFigmaNodeValue(config.desktopNodeId);
4278
+ const mobileNodeId = normalizeFigmaNodeValue(config.mobileNodeId);
4279
+ if (!desktopNodeId && !mobileNodeId) return null;
4280
+ return {
4281
+ desktopNodeId,
4282
+ mobileNodeId
4283
+ };
4284
+ } catch {
4285
+ return null;
4286
+ }
4287
+ }
4288
+ function getFigmaFrameUrl(config, preset) {
4289
+ if (!config) return null;
4290
+ const kind = getViewportPresetKind(preset);
4291
+ const value = kind === "mobile" ? config.mobileNodeId : config.desktopNodeId;
4292
+ return value ? createFigmaFrameUrl(value) : null;
4293
+ }
4294
+ function normalizeFigmaNodeValue(value) {
4295
+ return typeof value === "string" ? value.trim() || void 0 : void 0;
4296
+ }
4297
+ function createFigmaFrameUrl(value) {
4298
+ const [fileKey, nodeId] = value.split("->").map((part) => part.trim());
4299
+ if (!fileKey || !nodeId) return null;
4300
+ const urlNodeId = encodeURIComponent(nodeId.replace(/:/g, "-"));
4301
+ return `https://www.figma.com/design/${encodeURIComponent(
4302
+ fileKey
4303
+ )}?node-id=${urlNodeId}`;
4304
+ }
3693
4305
 
3694
4306
  // src/react-shell/qa/item.edit.modal.tsx
3695
- import { useEffect, useState } from "react";
4307
+ import { useEffect, useState as useState2 } from "react";
3696
4308
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3697
4309
  var QaItemEditModal = ({
3698
4310
  item,
3699
4311
  onClose,
3700
4312
  onSave
3701
4313
  }) => {
3702
- const [commentDraft, setCommentDraft] = useState(item.comment);
3703
- const [error, setError] = useState("");
3704
- const [isSaving, setIsSaving] = useState(false);
4314
+ const [commentDraft, setCommentDraft] = useState2(item.comment);
4315
+ const [error, setError] = useState2("");
4316
+ const [isSaving, setIsSaving] = useState2(false);
3705
4317
  useEffect(() => {
3706
4318
  setCommentDraft(item.comment);
3707
4319
  setError("");
@@ -4053,88 +4665,6 @@ var ReviewItemModeIcon = ({
4053
4665
  return /* @__PURE__ */ jsx7(StickyNote, { "aria-hidden": "true" });
4054
4666
  };
4055
4667
 
4056
- // src/react-shell/source.open.ts
4057
- var SOURCE_SELECTOR = [
4058
- "[data-wrk-source-file]",
4059
- "[data-wrk-source-component]",
4060
- "[data-wrk-source-line]",
4061
- "[data-wrk-source-column]",
4062
- "[data-file]",
4063
- "[data-component]",
4064
- "[data-section-index]",
4065
- "[data-section-id]"
4066
- ].join(", ");
4067
- var getSourceHintElement = (target) => {
4068
- return getEventElement(target)?.closest(SOURCE_SELECTOR) ?? null;
4069
- };
4070
- var getElementSourceHint = (target) => {
4071
- const sourceElement = getSourceHintElement(target);
4072
- if (!sourceElement) return void 0;
4073
- const source = {
4074
- component: getSourceAttribute(
4075
- sourceElement,
4076
- "data-wrk-source-component",
4077
- "data-component"
4078
- ),
4079
- file: getSourceAttribute(sourceElement, "data-wrk-source-file", "data-file"),
4080
- line: getSourceAttribute(sourceElement, "data-wrk-source-line"),
4081
- column: getSourceAttribute(sourceElement, "data-wrk-source-column"),
4082
- sectionId: getSourceAttribute(sourceElement, "data-section-id"),
4083
- sectionIndex: getSourceAttribute(sourceElement, "data-section-index")
4084
- };
4085
- return Object.values(source).some(Boolean) ? source : void 0;
4086
- };
4087
- var getSourceOpenUrl = (source, sourceRoot) => {
4088
- const file = source?.file?.trim();
4089
- if (!file) return null;
4090
- const sourcePath = getSourcePath(file, sourceRoot);
4091
- if (!sourcePath) return null;
4092
- const line = getSourcePosition(source?.line);
4093
- const column = getSourcePosition(source?.column);
4094
- const encodedPath = encodeURI(sourcePath).replace(
4095
- /[#?]/g,
4096
- (match) => match === "#" ? "%23" : "%3F"
4097
- );
4098
- return `vscode://file/${encodedPath}:${line}:${column}`;
4099
- };
4100
- var openSourceInEditor = (source, sourceRoot) => {
4101
- const url = getSourceOpenUrl(source, sourceRoot);
4102
- if (!url) return false;
4103
- window.open(url, "_blank", "noreferrer");
4104
- return true;
4105
- };
4106
- function getSourceAttribute(element, ...names) {
4107
- for (const name of names) {
4108
- const value = element.getAttribute(name)?.trim();
4109
- if (value) return value;
4110
- }
4111
- return void 0;
4112
- }
4113
- function getEventElement(target) {
4114
- if (!target || typeof target !== "object") return null;
4115
- const node = target;
4116
- if (node.nodeType === 1 && typeof node.closest === "function") {
4117
- return node;
4118
- }
4119
- if (node.parentElement && typeof node.parentElement.closest === "function") {
4120
- return node.parentElement;
4121
- }
4122
- return null;
4123
- }
4124
- function getSourcePath(file, sourceRoot) {
4125
- const normalizedFile = file.replace(/\\/g, "/");
4126
- if (normalizedFile.startsWith("/") || /^[a-zA-Z]:\//.test(normalizedFile)) {
4127
- return normalizedFile;
4128
- }
4129
- const normalizedRoot = sourceRoot?.trim().replace(/\\/g, "/").replace(/\/+$/, "");
4130
- if (!normalizedRoot) return null;
4131
- return `${normalizedRoot}/${normalizedFile.replace(/^\/+/, "")}`;
4132
- }
4133
- function getSourcePosition(value) {
4134
- const position = Number(value);
4135
- return Number.isInteger(position) && position > 0 ? position : 1;
4136
- }
4137
-
4138
4668
  // src/react-shell/qa/item.card.tsx
4139
4669
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
4140
4670
  var formatItemCardDate = (value) => {
@@ -4157,10 +4687,10 @@ var QaItemCard = ({
4157
4687
  remoteAdapterEntry,
4158
4688
  copiedPromptKey,
4159
4689
  selectedItemId,
4160
- sourceRoot,
4161
4690
  onChangeItemStatus,
4162
4691
  onClearSelectedItem,
4163
4692
  onRemoveItem,
4693
+ onCopyItemLink,
4164
4694
  onCopyItemPrompt,
4165
4695
  onEditItem,
4166
4696
  onRestoreReviewItem,
@@ -4175,13 +4705,14 @@ var QaItemCard = ({
4175
4705
  const itemComment = item.comment.trim() || getItemTitle(item);
4176
4706
  const itemAuthor = item.createdBy?.trim();
4177
4707
  const promptCopyKey = `qa:${item.id}`;
4708
+ const linkCopyKey = `link:${item.id}`;
4178
4709
  const isPromptCopied = copiedPromptKey === promptCopyKey;
4710
+ const isLinkCopied = copiedPromptKey === linkCopyKey;
4179
4711
  const statusOptions = activeAdapterEntry.statusOptions;
4180
4712
  const isActive = item.id === selectedItemId;
4181
4713
  const canUpdateStatus = Boolean(activeAdapterEntry.updateStatus) && statusOptions.length > 0 && !isSubmitting;
4182
4714
  const canEditItem = activeAdapterEntry.canUpdate && !isSubmitting;
4183
4715
  const itemMeta = [formatItemCardDate(item.createdAt), itemAuthor].filter(Boolean).join(" | ");
4184
- const sourceOpenUrl = getSourceOpenUrl(item.anchor?.source, sourceRoot);
4185
4716
  return /* @__PURE__ */ jsxs6(
4186
4717
  "article",
4187
4718
  {
@@ -4233,27 +4764,15 @@ var QaItemCard = ({
4233
4764
  children: isOverlayVisible ? /* @__PURE__ */ jsx8(Eye, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx8(EyeOff, { "aria-hidden": "true" })
4234
4765
  }
4235
4766
  ),
4236
- sourceOpenUrl && /* @__PURE__ */ jsx8(
4237
- "a",
4238
- {
4239
- "aria-label": "Open source in VS Code",
4240
- className: "df-review-item-source-open",
4241
- href: sourceOpenUrl,
4242
- rel: "noreferrer",
4243
- target: "_blank",
4244
- title: "Open source in VS Code",
4245
- children: /* @__PURE__ */ jsx8(FileCodeCorner, { "aria-hidden": "true" })
4246
- }
4247
- ),
4248
4767
  /* @__PURE__ */ jsx8(
4249
4768
  "button",
4250
4769
  {
4251
- "aria-label": isPromptCopied ? "Copied QA prompt" : "Copy QA prompt",
4252
- className: `df-review-item-prompt-copy${isPromptCopied ? " is-copied" : ""}`,
4253
- title: isPromptCopied ? "Copied QA prompt" : "Copy QA prompt",
4770
+ "aria-label": isLinkCopied ? "Copied QA link" : "Copy QA link",
4771
+ className: `df-review-item-link-copy${isLinkCopied ? " is-copied" : ""}`,
4772
+ title: isLinkCopied ? "Copied QA link" : "Copy QA link",
4254
4773
  type: "button",
4255
- onClick: () => onCopyItemPrompt(numberedItem),
4256
- children: /* @__PURE__ */ jsx8(Copy, { "aria-hidden": "true" })
4774
+ onClick: () => onCopyItemLink(numberedItem),
4775
+ children: /* @__PURE__ */ jsx8(Link2, { "aria-hidden": "true" })
4257
4776
  }
4258
4777
  ),
4259
4778
  canEditItem && /* @__PURE__ */ jsx8(
@@ -4291,6 +4810,24 @@ var QaItemCard = ({
4291
4810
  onChangeItemStatus
4292
4811
  }
4293
4812
  ),
4813
+ /* @__PURE__ */ jsx8(
4814
+ "div",
4815
+ {
4816
+ className: "df-review-item-prompt-actions",
4817
+ onClick: (event) => event.stopPropagation(),
4818
+ children: /* @__PURE__ */ jsx8(
4819
+ "button",
4820
+ {
4821
+ "aria-label": isPromptCopied ? "Copied QA prompt" : "Copy QA prompt",
4822
+ className: `df-review-item-action-button df-review-item-prompt-copy${isPromptCopied ? " is-copied" : ""}`,
4823
+ title: isPromptCopied ? "Copied QA prompt" : "Copy QA prompt",
4824
+ type: "button",
4825
+ onClick: () => onCopyItemPrompt(numberedItem),
4826
+ children: isPromptCopied ? /* @__PURE__ */ jsx8(Copy, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx8(Bot, { "aria-hidden": "true" })
4827
+ }
4828
+ )
4829
+ }
4830
+ ),
4294
4831
  /* @__PURE__ */ jsx8(
4295
4832
  QaItemRemoteActions,
4296
4833
  {
@@ -4309,81 +4846,57 @@ var QaItemCard = ({
4309
4846
  );
4310
4847
  };
4311
4848
 
4312
- // src/react-shell/presence/row.tsx
4313
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
4314
- var PresenceRow = ({
4315
- presenceSessionId,
4316
- users
4317
- }) => {
4318
- if (users.length === 0) return null;
4319
- return /* @__PURE__ */ jsxs7("div", { "aria-label": "Review presence", className: "df-review-presence-row", children: [
4320
- /* @__PURE__ */ jsxs7("span", { className: "df-review-presence-label", children: [
4321
- /* @__PURE__ */ jsx9(Users, { "aria-hidden": "true" }),
4322
- "online ",
4323
- users.length
4324
- ] }),
4325
- /* @__PURE__ */ jsx9("div", { className: "df-review-presence-list", children: users.map((user) => /* @__PURE__ */ jsxs7(
4326
- "span",
4327
- {
4328
- className: `df-review-presence-chip${user.sessionId === presenceSessionId ? " is-self" : ""}`,
4329
- style: {
4330
- "--df-review-presence-color": user.color
4331
- },
4332
- children: [
4333
- /* @__PURE__ */ jsx9("span", { className: "df-review-presence-dot", "aria-hidden": "true" }),
4334
- /* @__PURE__ */ jsx9("span", { className: "df-review-presence-name", children: user.userId })
4335
- ]
4336
- },
4337
- user.sessionId
4338
- )) })
4339
- ] });
4340
- };
4341
-
4342
4849
  // src/react-shell/qa/panel.header.tsx
4343
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
4850
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
4344
4851
  var QaPanelHeader = ({
4345
4852
  activeItemCount,
4346
- currentPagePresenceUsers,
4853
+ activeRemainingItemCount,
4347
4854
  filteredItemCount,
4855
+ isAllQaVisible,
4348
4856
  label,
4349
- presenceSessionId,
4350
4857
  qaFilter,
4351
4858
  qaFilterCounts,
4859
+ qaStatusFilter,
4860
+ qaStatusFilterCounts,
4352
4861
  showSourceSelect,
4353
4862
  source,
4354
4863
  sourceEntries,
4864
+ statusOptions,
4355
4865
  onChangeReviewSource,
4356
4866
  onQaFilterChange,
4867
+ onQaStatusFilterChange,
4357
4868
  onRefreshReviewData
4358
4869
  }) => {
4359
- return /* @__PURE__ */ jsxs8("div", { className: "df-review-list-header", children: [
4360
- /* @__PURE__ */ jsxs8("div", { className: "df-review-list-toolbar", children: [
4361
- /* @__PURE__ */ jsxs8("div", { className: "df-review-list-controls", children: [
4362
- showSourceSelect && /* @__PURE__ */ jsx10(
4870
+ const statusFilterOptions = getStatusFilterOptions(statusOptions);
4871
+ const hasActiveFilter = qaFilter !== "all" || qaStatusFilter !== "all";
4872
+ return /* @__PURE__ */ jsxs7("div", { className: "df-review-list-header", children: [
4873
+ /* @__PURE__ */ jsxs7("div", { className: "df-review-list-toolbar", children: [
4874
+ /* @__PURE__ */ jsxs7("div", { className: "df-review-list-controls", children: [
4875
+ showSourceSelect && /* @__PURE__ */ jsx9(
4363
4876
  "select",
4364
4877
  {
4365
4878
  "aria-label": "QA source",
4366
4879
  className: "df-review-source-select",
4367
4880
  value: source,
4368
4881
  onChange: (event) => onChangeReviewSource(event.currentTarget.value),
4369
- children: sourceEntries.map((entry) => /* @__PURE__ */ jsx10("option", { value: entry.label, children: entry.label }, entry.label))
4882
+ children: sourceEntries.map((entry) => /* @__PURE__ */ jsx9("option", { value: entry.label, children: entry.label }, entry.label))
4370
4883
  }
4371
4884
  ),
4372
- /* @__PURE__ */ jsx10(
4885
+ /* @__PURE__ */ jsx9(
4373
4886
  "button",
4374
4887
  {
4375
4888
  "aria-label": "Refresh QA",
4376
4889
  className: "df-review-source-refresh",
4377
4890
  type: "button",
4378
4891
  onClick: () => void onRefreshReviewData(),
4379
- children: /* @__PURE__ */ jsx10(RefreshCw, { "aria-hidden": "true" })
4892
+ children: /* @__PURE__ */ jsx9(RefreshCw, { "aria-hidden": "true" })
4380
4893
  }
4381
4894
  )
4382
4895
  ] }),
4383
- /* @__PURE__ */ jsx10("div", { className: "df-review-filter-tabs", "aria-label": "QA filters", children: REVIEW_QA_FILTERS.map((filter) => {
4896
+ /* @__PURE__ */ jsx9("div", { className: "df-review-filter-tabs", "aria-label": "QA filters", children: REVIEW_QA_FILTERS.map((filter) => {
4384
4897
  const count = qaFilterCounts.get(filter.key) ?? 0;
4385
4898
  const isActive = qaFilter === filter.key;
4386
- return /* @__PURE__ */ jsx10(
4899
+ return /* @__PURE__ */ jsx9(
4387
4900
  "button",
4388
4901
  {
4389
4902
  "aria-label": `${filter.label} QA (${count})`,
@@ -4391,86 +4904,108 @@ var QaPanelHeader = ({
4391
4904
  className: `df-review-filter-tab${isActive ? " is-active" : ""}`,
4392
4905
  type: "button",
4393
4906
  onClick: () => onQaFilterChange(filter.key),
4394
- children: /* @__PURE__ */ jsx10("span", { className: "df-review-filter-icon", children: filter.scope ? /* @__PURE__ */ jsx10(ReviewScopeIcon, { scope: filter.scope }) : /* @__PURE__ */ jsx10(ListFilter, { "aria-hidden": "true" }) })
4907
+ children: /* @__PURE__ */ jsx9("span", { className: "df-review-filter-icon", children: filter.scope ? /* @__PURE__ */ jsx9(ReviewScopeIcon, { scope: filter.scope }) : /* @__PURE__ */ jsx9(ListFilter, { "aria-hidden": "true" }) })
4395
4908
  },
4396
4909
  filter.key
4397
4910
  );
4398
4911
  }) })
4399
4912
  ] }),
4400
- /* @__PURE__ */ jsxs8("div", { className: "df-review-list-title", children: [
4401
- /* @__PURE__ */ jsxs8("span", { children: [
4402
- label,
4403
- " QA"
4404
- ] }),
4405
- /* @__PURE__ */ jsxs8("strong", { children: [
4406
- filteredItemCount,
4407
- qaFilter === "all" ? "" : `/${activeItemCount}`
4408
- ] })
4409
- ] }),
4410
- /* @__PURE__ */ jsx10(
4411
- PresenceRow,
4412
- {
4413
- presenceSessionId,
4414
- users: currentPagePresenceUsers
4415
- }
4416
- )
4913
+ /* @__PURE__ */ jsxs7("div", { className: "df-review-list-title", children: [
4914
+ /* @__PURE__ */ jsx9("span", { children: isAllQaVisible ? `${label} QA \xB7 All pages` : `${label} QA` }),
4915
+ /* @__PURE__ */ jsx9("strong", { title: `${activeRemainingItemCount} remaining of ${activeItemCount}`, children: !hasActiveFilter ? `${activeRemainingItemCount}/${activeItemCount}` : `${filteredItemCount}/${activeItemCount}` }),
4916
+ /* @__PURE__ */ jsxs7(
4917
+ "select",
4918
+ {
4919
+ "aria-label": "QA status filter",
4920
+ className: "df-review-status-filter-select",
4921
+ value: qaStatusFilter,
4922
+ onChange: (event) => onQaStatusFilterChange(
4923
+ event.currentTarget.value
4924
+ ),
4925
+ children: [
4926
+ /* @__PURE__ */ jsx9("option", { value: "all", children: `All status (${qaStatusFilterCounts.get("all") ?? 0})` }),
4927
+ statusFilterOptions.map((statusOption) => /* @__PURE__ */ jsx9("option", { value: statusOption.value, children: `${statusOption.label} (${qaStatusFilterCounts.get(statusOption.value) ?? 0})` }, statusOption.value))
4928
+ ]
4929
+ }
4930
+ )
4931
+ ] })
4417
4932
  ] });
4418
4933
  };
4934
+ function getStatusFilterOptions(statusOptions) {
4935
+ const seen = /* @__PURE__ */ new Set();
4936
+ return statusOptions.flatMap((statusOption) => {
4937
+ const value = normalizeReviewItemStatus(statusOption.value);
4938
+ if (seen.has(value)) return [];
4939
+ seen.add(value);
4940
+ return [{
4941
+ value,
4942
+ label: statusOption.label
4943
+ }];
4944
+ });
4945
+ }
4419
4946
 
4420
4947
  // src/react-shell/qa/panel.tsx
4421
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
4948
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
4422
4949
  var ReviewQaPanel = ({
4423
4950
  activeAdapterEntry,
4424
4951
  activeItems,
4425
- currentPagePresenceUsers,
4952
+ activeRemainingItemCount,
4426
4953
  currentPresetScope,
4427
4954
  filteredNumberedActiveItems,
4428
4955
  getItemPresetScope,
4429
4956
  hiddenOverlayItemIds,
4957
+ isAllQaVisible,
4430
4958
  isListVisible,
4431
4959
  isRemoteSource,
4432
- presenceSessionId,
4433
4960
  copiedPromptKey,
4434
4961
  qaFilter,
4435
4962
  qaFilterCounts,
4963
+ qaStatusFilter,
4964
+ qaStatusFilterCounts,
4436
4965
  remoteAdapterEntry,
4437
4966
  selectedItemId,
4438
4967
  showSourceSelect,
4439
- sourceRoot,
4440
4968
  source,
4441
4969
  sourceEntries,
4442
4970
  onChangeItemStatus,
4443
4971
  onClearSelectedItem,
4444
4972
  onChangeReviewSource,
4973
+ onCopyItemLink,
4445
4974
  onCopyItemPrompt,
4446
4975
  onEditItem,
4447
4976
  onQaFilterChange,
4977
+ onQaStatusFilterChange,
4448
4978
  onRefreshReviewData,
4449
4979
  onRemoveItem,
4450
4980
  onRestoreReviewItem,
4451
4981
  onSubmitItem,
4452
4982
  onToggleItemOverlayVisibility
4453
4983
  }) => {
4454
- return /* @__PURE__ */ jsx11("aside", { className: "df-review-qa-panel", "aria-hidden": !isListVisible, children: /* @__PURE__ */ jsx11("div", { className: "df-review-panel-body", children: /* @__PURE__ */ jsxs9("section", { className: "df-review-item-list", children: [
4455
- /* @__PURE__ */ jsx11(
4984
+ const emptyMessage = isAllQaVisible ? `No ${activeAdapterEntry.label} QA.` : isRemoteSource ? `No ${activeAdapterEntry.label} QA on this page.` : "No QA on this page.";
4985
+ return /* @__PURE__ */ jsx10("aside", { className: "df-review-qa-panel", "aria-hidden": !isListVisible, children: /* @__PURE__ */ jsx10("div", { className: "df-review-panel-body", children: /* @__PURE__ */ jsxs8("section", { className: "df-review-item-list", children: [
4986
+ /* @__PURE__ */ jsx10(
4456
4987
  QaPanelHeader,
4457
4988
  {
4458
4989
  activeItemCount: activeItems.length,
4459
- currentPagePresenceUsers,
4990
+ activeRemainingItemCount,
4460
4991
  filteredItemCount: filteredNumberedActiveItems.length,
4992
+ isAllQaVisible,
4461
4993
  label: activeAdapterEntry.label,
4462
- presenceSessionId,
4463
4994
  qaFilter,
4464
4995
  qaFilterCounts,
4996
+ qaStatusFilter,
4997
+ qaStatusFilterCounts,
4465
4998
  showSourceSelect,
4466
4999
  source,
4467
5000
  sourceEntries,
5001
+ statusOptions: activeAdapterEntry.statusOptions,
4468
5002
  onChangeReviewSource,
4469
5003
  onQaFilterChange,
5004
+ onQaStatusFilterChange,
4470
5005
  onRefreshReviewData
4471
5006
  }
4472
5007
  ),
4473
- /* @__PURE__ */ jsxs9(
5008
+ /* @__PURE__ */ jsxs8(
4474
5009
  "div",
4475
5010
  {
4476
5011
  className: "df-review-list-scroll",
@@ -4480,11 +5015,11 @@ var ReviewQaPanel = ({
4480
5015
  }
4481
5016
  },
4482
5017
  children: [
4483
- activeItems.length === 0 && /* @__PURE__ */ jsx11("p", { className: "df-review-empty", children: isRemoteSource ? `No ${activeAdapterEntry.label} QA on this page.` : "No QA on this page." }),
4484
- activeItems.length > 0 && filteredNumberedActiveItems.length === 0 && /* @__PURE__ */ jsx11("p", { className: "df-review-empty", children: "No QA in this filter." }),
5018
+ activeItems.length === 0 && /* @__PURE__ */ jsx10("p", { className: "df-review-empty", children: emptyMessage }),
5019
+ activeItems.length > 0 && filteredNumberedActiveItems.length === 0 && /* @__PURE__ */ jsx10("p", { className: "df-review-empty", children: "No QA in this filter." }),
4485
5020
  filteredNumberedActiveItems.map((numberedItem) => {
4486
5021
  const { item } = numberedItem;
4487
- return /* @__PURE__ */ jsx11(
5022
+ return /* @__PURE__ */ jsx10(
4488
5023
  QaItemCard,
4489
5024
  {
4490
5025
  activeAdapterEntry,
@@ -4496,9 +5031,9 @@ var ReviewQaPanel = ({
4496
5031
  remoteAdapterEntry,
4497
5032
  copiedPromptKey,
4498
5033
  selectedItemId,
4499
- sourceRoot,
4500
5034
  onChangeItemStatus,
4501
5035
  onClearSelectedItem,
5036
+ onCopyItemLink,
4502
5037
  onCopyItemPrompt,
4503
5038
  onEditItem,
4504
5039
  onRemoveItem,
@@ -4511,9 +5046,266 @@ var ReviewQaPanel = ({
4511
5046
  })
4512
5047
  ]
4513
5048
  }
4514
- )
4515
- ] }) }) });
5049
+ )
5050
+ ] }) }) });
5051
+ };
5052
+
5053
+ // src/react-shell/presence/overlay.tsx
5054
+ import { useState as useState3 } from "react";
5055
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
5056
+ var COLLAPSED_USER_COUNT = 1;
5057
+ var getPresenceName = (user) => user.displayName || user.userId;
5058
+ var PresenceOverlay = ({
5059
+ presenceSessionId,
5060
+ users
5061
+ }) => {
5062
+ const [isExpanded, setIsExpanded] = useState3(false);
5063
+ if (users.length === 0) return null;
5064
+ const visibleUsers = isExpanded ? users : users.slice(0, COLLAPSED_USER_COUNT);
5065
+ const hiddenUserCount = users.length - visibleUsers.length;
5066
+ return /* @__PURE__ */ jsxs9(
5067
+ "div",
5068
+ {
5069
+ "aria-label": `Review presence, ${users.length} online`,
5070
+ className: `df-review-presence-overlay${isExpanded ? " is-expanded" : ""}`,
5071
+ children: [
5072
+ visibleUsers.map((user) => /* @__PURE__ */ jsx11(
5073
+ "span",
5074
+ {
5075
+ className: `df-review-presence-chip${user.sessionId === presenceSessionId ? " is-self" : ""}`,
5076
+ style: {
5077
+ "--df-review-presence-color": user.color
5078
+ },
5079
+ title: getPresenceName(user),
5080
+ children: getPresenceName(user)
5081
+ },
5082
+ user.sessionId
5083
+ )),
5084
+ hiddenUserCount > 0 && /* @__PURE__ */ jsxs9(
5085
+ "button",
5086
+ {
5087
+ "aria-label": `Show ${hiddenUserCount} more online reviewers`,
5088
+ "aria-expanded": isExpanded,
5089
+ className: "df-review-presence-more",
5090
+ type: "button",
5091
+ onClick: () => setIsExpanded(true),
5092
+ children: [
5093
+ "+",
5094
+ hiddenUserCount
5095
+ ]
5096
+ }
5097
+ )
5098
+ ]
5099
+ }
5100
+ );
5101
+ };
5102
+
5103
+ // src/react-shell/source.open.ts
5104
+ var SOURCE_SELECTOR = [
5105
+ "[data-wrk-source-file]",
5106
+ "[data-wrk-source-component]",
5107
+ "[data-wrk-source-line]",
5108
+ "[data-wrk-source-column]",
5109
+ "[data-file]",
5110
+ "[data-component]",
5111
+ "[data-section-index]",
5112
+ "[data-section-id]"
5113
+ ].join(", ");
5114
+ var getSourceCandidates = (target) => {
5115
+ const startElement = getEventElement(target);
5116
+ if (!startElement) return [];
5117
+ const candidates = [];
5118
+ const seen = /* @__PURE__ */ new Set();
5119
+ let element = startElement;
5120
+ let depth = 0;
5121
+ while (element && element.nodeType === 1) {
5122
+ const source = getSourceHintFromElement(element);
5123
+ if (source?.file) {
5124
+ const key = getSourceCandidateKey(source);
5125
+ if (!seen.has(key)) {
5126
+ seen.add(key);
5127
+ candidates.push(createSourceCandidate(element, source, depth));
5128
+ }
5129
+ }
5130
+ if (element === element.ownerDocument.documentElement) break;
5131
+ element = element.parentElement;
5132
+ depth += 1;
5133
+ }
5134
+ return candidates.slice(0, 8);
4516
5135
  };
5136
+ var getSourceOpenUrl = (source, options) => {
5137
+ const normalizedOptions = normalizeSourceOpenOptions(options);
5138
+ const file = source?.file?.trim();
5139
+ if (!file) return null;
5140
+ const sourcePath = getSourcePath(file, normalizedOptions.sourceRoot);
5141
+ if (!sourcePath) return null;
5142
+ const hasPosition = !normalizedOptions.omitPosition;
5143
+ const line = hasPosition ? getSourcePosition(source?.line) : null;
5144
+ const column = hasPosition ? getSourcePosition(source?.column) : null;
5145
+ const editor = normalizedOptions.editor ?? "vscode";
5146
+ if (normalizedOptions.urlTemplate) {
5147
+ return buildSourceUrlFromTemplate(normalizedOptions.urlTemplate, {
5148
+ column,
5149
+ file,
5150
+ line,
5151
+ sourcePath,
5152
+ sourceRoot: normalizedOptions.sourceRoot
5153
+ });
5154
+ }
5155
+ if (editor === "webstorm") {
5156
+ const params = new URLSearchParams({ file: sourcePath });
5157
+ if (line) params.set("line", String(line));
5158
+ if (column) params.set("column", String(column));
5159
+ return `webstorm://open?${params.toString()}`;
5160
+ }
5161
+ if (editor === "custom") return null;
5162
+ const encodedPath = encodePathForFileScheme(sourcePath);
5163
+ const scheme = editor === "cursor" ? "cursor" : "vscode";
5164
+ if (!line) return `${scheme}://file/${encodedPath}`;
5165
+ if (!column) return `${scheme}://file/${encodedPath}:${line}`;
5166
+ return `${scheme}://file/${encodedPath}:${line}:${column}`;
5167
+ };
5168
+ var openSourceInEditor = (source, options) => {
5169
+ const url = getSourceOpenUrl(source, options);
5170
+ if (!url) return false;
5171
+ window.open(url, "_blank", "noreferrer");
5172
+ return true;
5173
+ };
5174
+ function getSourceHintFromElement(element) {
5175
+ const source = {
5176
+ component: getSourceAttribute(
5177
+ element,
5178
+ "data-wrk-source-component",
5179
+ "data-component"
5180
+ ),
5181
+ file: getSourceAttribute(element, "data-wrk-source-file", "data-file"),
5182
+ line: getSourceAttribute(element, "data-wrk-source-line"),
5183
+ column: getSourceAttribute(element, "data-wrk-source-column"),
5184
+ sectionId: getSourceAttribute(element, "data-section-id"),
5185
+ sectionIndex: getSourceAttribute(element, "data-section-index")
5186
+ };
5187
+ return Object.values(source).some(Boolean) ? source : void 0;
5188
+ }
5189
+ function createSourceCandidate(element, source, depth) {
5190
+ const confidence = getSourceConfidence(source, depth);
5191
+ const fileName = source.file?.split(/[\\/]/).pop() ?? source.file ?? "source";
5192
+ const component = source.component?.trim();
5193
+ const fallbackComponent = getComponentNameFromSourceFile(source.file);
5194
+ const tag = element.tagName.toLowerCase();
5195
+ const line = getSourcePosition(source.line);
5196
+ const column = getSourcePosition(source.column);
5197
+ const position = line ? `:${line}${column ? `:${column}` : ""}` : "";
5198
+ return {
5199
+ id: getSourceCandidateKey(source),
5200
+ depth,
5201
+ element,
5202
+ filePath: getDisplaySourcePath(source.file) ?? fileName,
5203
+ label: component || fallbackComponent || tag,
5204
+ detail: `${fileName}${position}`,
5205
+ positionLabel: line ? `${line}:${column ?? 1}` : "",
5206
+ confidence,
5207
+ confidenceLabel: confidence >= 0.82 ? "high" : confidence >= 0.58 ? "medium" : "low",
5208
+ usesPosition: confidence >= 0.72 && Boolean(line),
5209
+ source
5210
+ };
5211
+ }
5212
+ function getComponentNameFromSourceFile(file) {
5213
+ const normalizedFile = file?.trim().replace(/\\/g, "/");
5214
+ if (!normalizedFile) return void 0;
5215
+ const pathParts = normalizedFile.split("/").filter(Boolean);
5216
+ const fileName = pathParts[pathParts.length - 1];
5217
+ if (!fileName) return void 0;
5218
+ const stem = fileName.replace(/\.[^.]+$/, "");
5219
+ const sourceName = stem.toLowerCase() === "index" ? pathParts[pathParts.length - 2]?.replace(/\.[^.]+$/, "") : stem;
5220
+ return toPascalCase2(sourceName);
5221
+ }
5222
+ function toPascalCase2(value) {
5223
+ const words = value?.split(/[._\-\s]+/).map((part) => part.trim()).filter(Boolean);
5224
+ if (!words?.length) return void 0;
5225
+ return words.map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join("");
5226
+ }
5227
+ function getDisplaySourcePath(file) {
5228
+ const normalizedFile = file?.trim().replace(/\\/g, "/");
5229
+ if (!normalizedFile) return void 0;
5230
+ const sourceRootMatch = normalizedFile.match(
5231
+ /(?:^|\/)((?:dev\/)?src\/.+|app\/.+|pages\/.+|components\/.+)$/
5232
+ );
5233
+ return sourceRootMatch?.[1] ?? normalizedFile;
5234
+ }
5235
+ function getSourceCandidateKey(source) {
5236
+ return [
5237
+ source.file,
5238
+ source.component,
5239
+ source.line,
5240
+ source.column,
5241
+ source.sectionId,
5242
+ source.sectionIndex
5243
+ ].filter(Boolean).join("|");
5244
+ }
5245
+ function getSourceConfidence(source, depth) {
5246
+ let score = source.file ? 0.54 : 0.12;
5247
+ if (source.component) score += 0.12;
5248
+ if (getSourcePosition(source.line)) score += 0.22;
5249
+ if (getSourcePosition(source.column)) score += 0.08;
5250
+ if (source.sectionId || source.sectionIndex) score += 0.04;
5251
+ score -= Math.min(depth, 5) * 0.045;
5252
+ return Math.max(0.1, Math.min(1, Number(score.toFixed(2))));
5253
+ }
5254
+ function normalizeSourceOpenOptions(options) {
5255
+ return typeof options === "string" ? { sourceRoot: options } : options ?? {};
5256
+ }
5257
+ function buildSourceUrlFromTemplate(template, values) {
5258
+ const replacements = {
5259
+ column: values.column ? String(values.column) : "",
5260
+ encodedPath: encodeURIComponent(values.sourcePath),
5261
+ file: values.file,
5262
+ line: values.line ? String(values.line) : "",
5263
+ path: values.sourcePath,
5264
+ sourceRoot: values.sourceRoot ?? "",
5265
+ uriPath: encodePathForFileScheme(values.sourcePath)
5266
+ };
5267
+ return template.replace(
5268
+ /\{([a-zA-Z]+)\}/g,
5269
+ (_, key) => replacements[key] ?? ""
5270
+ );
5271
+ }
5272
+ function getSourceAttribute(element, ...names) {
5273
+ for (const name of names) {
5274
+ const value = element.getAttribute(name)?.trim();
5275
+ if (value) return value;
5276
+ }
5277
+ return void 0;
5278
+ }
5279
+ function getEventElement(target) {
5280
+ if (!target || typeof target !== "object") return null;
5281
+ const node = target;
5282
+ if (node.nodeType === 1 && typeof node.closest === "function") {
5283
+ return node;
5284
+ }
5285
+ if (node.parentElement && typeof node.parentElement.closest === "function") {
5286
+ return node.parentElement;
5287
+ }
5288
+ return null;
5289
+ }
5290
+ function getSourcePath(file, sourceRoot) {
5291
+ const normalizedFile = file.replace(/\\/g, "/");
5292
+ if (normalizedFile.startsWith("/") || /^[a-zA-Z]:\//.test(normalizedFile)) {
5293
+ return normalizedFile;
5294
+ }
5295
+ const normalizedRoot = sourceRoot?.trim().replace(/\\/g, "/").replace(/\/+$/, "");
5296
+ if (!normalizedRoot) return null;
5297
+ return `${normalizedRoot}/${normalizedFile.replace(/^\/+/, "")}`;
5298
+ }
5299
+ function getSourcePosition(value) {
5300
+ const position = Number(value);
5301
+ return Number.isInteger(position) && position > 0 ? position : null;
5302
+ }
5303
+ function encodePathForFileScheme(path) {
5304
+ return encodeURI(path).replace(
5305
+ /[#?]/g,
5306
+ (match) => match === "#" ? "%23" : "%3F"
5307
+ );
5308
+ }
4517
5309
 
4518
5310
  // src/react-shell/review/mode.toolbar.tsx
4519
5311
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
@@ -4693,6 +5485,7 @@ var ReviewTargetFrame = ({
4693
5485
  canWriteArea,
4694
5486
  canWriteDom,
4695
5487
  frameScrollRef,
5488
+ figmaFrameUrl,
4696
5489
  iframeRef,
4697
5490
  isRulerAvailable,
4698
5491
  isRulerDragging,
@@ -4711,8 +5504,9 @@ var ReviewTargetFrame = ({
4711
5504
  onSetReviewMode
4712
5505
  }) => {
4713
5506
  const showRuler = isRulerVisible && isRulerAvailable;
5507
+ const targetHref = getTargetOpenHref(targetSrc);
4714
5508
  return /* @__PURE__ */ jsx15("main", { className: "df-review-stage", children: /* @__PURE__ */ jsxs13("div", { className: "df-review-frame", children: [
4715
- /* @__PURE__ */ jsx15("div", { className: "df-review-frame-scroll", ref: frameScrollRef, children: /* @__PURE__ */ jsx15("div", { className: "df-review-frame-canvas", children: /* @__PURE__ */ jsxs13(
5509
+ /* @__PURE__ */ jsx15("div", { className: "df-review-frame-scroll", ref: frameScrollRef, children: /* @__PURE__ */ jsx15("div", { className: "df-review-frame-canvas", children: /* @__PURE__ */ jsx15("div", { className: "df-review-target-stack", children: /* @__PURE__ */ jsxs13(
4716
5510
  "div",
4717
5511
  {
4718
5512
  className: `df-review-device-frame${showRuler ? " is-ruler" : ""}`,
@@ -4764,10 +5558,36 @@ var ReviewTargetFrame = ({
4764
5558
  )
4765
5559
  ]
4766
5560
  }
4767
- )
5561
+ ),
5562
+ /* @__PURE__ */ jsxs13("div", { className: "df-review-frame-link-stack", children: [
5563
+ /* @__PURE__ */ jsx15(
5564
+ "a",
5565
+ {
5566
+ "aria-label": "Open target page",
5567
+ className: "df-review-frame-link is-target",
5568
+ href: targetHref,
5569
+ rel: "noreferrer",
5570
+ target: "_blank",
5571
+ title: "Open target page",
5572
+ children: /* @__PURE__ */ jsx15(ExternalLink, { "aria-hidden": "true" })
5573
+ }
5574
+ ),
5575
+ figmaFrameUrl && /* @__PURE__ */ jsx15(
5576
+ "a",
5577
+ {
5578
+ "aria-label": "Open Figma frame",
5579
+ className: "df-review-frame-link is-figma",
5580
+ href: figmaFrameUrl,
5581
+ rel: "noreferrer",
5582
+ target: "_blank",
5583
+ title: "Open Figma frame",
5584
+ children: /* @__PURE__ */ jsx15(FigmaIcon, {})
5585
+ }
5586
+ )
5587
+ ] })
4768
5588
  ]
4769
5589
  }
4770
- ) }) }),
5590
+ ) }) }) }),
4771
5591
  /* @__PURE__ */ jsx15("div", { className: "df-review-frame-actions", children: /* @__PURE__ */ jsx15(
4772
5592
  ReviewModeToolbar,
4773
5593
  {
@@ -4779,6 +5599,20 @@ var ReviewTargetFrame = ({
4779
5599
  ) })
4780
5600
  ] }) });
4781
5601
  };
5602
+ function getTargetOpenHref(targetSrc) {
5603
+ const url = new URL(targetSrc, window.location.origin);
5604
+ url.searchParams.delete("__dfwr_target");
5605
+ return `${url.pathname}${url.search}${url.hash}`;
5606
+ }
5607
+ var FigmaIcon = () => /* @__PURE__ */ jsx15(
5608
+ "svg",
5609
+ {
5610
+ "aria-hidden": "true",
5611
+ viewBox: "0 0 24 24",
5612
+ xmlns: "http://www.w3.org/2000/svg",
5613
+ children: /* @__PURE__ */ jsx15("path", { d: "M15.852 8.981h-4.588V0h4.588c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.491-4.49 4.491zM12.735 7.51h3.117c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-3.117V7.51zm0 1.471H8.148c-2.476 0-4.49-2.014-4.49-4.49S5.672 0 8.148 0h4.588v8.981zm-4.587-7.51c-1.665 0-3.019 1.355-3.019 3.019s1.354 3.02 3.019 3.02h3.117V1.471H8.148zm4.587 15.019H8.148c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h4.588v8.98zM8.148 8.981c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h3.117V8.981H8.148zM8.172 24c-2.489 0-4.515-2.014-4.515-4.49s2.014-4.49 4.49-4.49h4.588v4.441C12.735 21.964 10.688 24 8.172 24zm-.024-7.51a3.023 3.023 0 0 0-3.019 3.019c0 1.665 1.365 3.019 3.044 3.019 1.705 0 3.093-1.376 3.093-3.068v-2.97H8.148zm7.704 0h-.098c-2.476 0-4.49-2.014-4.49-4.49s2.014-4.49 4.49-4.49h.098c2.476 0 4.49 2.014 4.49 4.49s-2.014 4.49-4.49 4.49zm-.097-7.509c-1.665 0-3.019 1.355-3.019 3.019s1.355 3.019 3.019 3.019h.098c1.665 0 3.019-1.355 3.019-3.019s-1.355-3.019-3.019-3.019h-.098z" })
5614
+ }
5615
+ );
4782
5616
 
4783
5617
  // src/react-shell/topbar.tsx
4784
5618
  import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
@@ -4897,6 +5731,7 @@ var ReviewTopbar = ({
4897
5731
  "aria-disabled": !isFigmaOverlayAvailable,
4898
5732
  "aria-label": isFigmaOverlayAvailable ? "Toggle Figma overlay" : FIGMA_OVERLAY_UNAVAILABLE_MESSAGE,
4899
5733
  className: `df-review-overlay-button is-figma${targetOverlayState.figma ? " is-active" : ""}${isFigmaOverlayAvailable ? "" : " is-disabled"}`,
5734
+ disabled: !isFigmaOverlayAvailable,
4900
5735
  type: "button",
4901
5736
  onClick: () => onToggleTargetOverlay("figma"),
4902
5737
  children: /* @__PURE__ */ jsx16(Image, { "aria-hidden": "true" })
@@ -4954,6 +5789,47 @@ function runWithAutoScrollBehavior(targetDocument, callback) {
4954
5789
  });
4955
5790
  }
4956
5791
  }
5792
+ var RESTORE_WAIT_MAX_MS = 2600;
5793
+ var RESTORE_STABLE_FRAME_COUNT = 2;
5794
+ var waitForNextAnimationFrame = (targetWindow) => new Promise((resolve) => {
5795
+ targetWindow.requestAnimationFrame(() => resolve());
5796
+ });
5797
+ var getRestoreLayoutSnapshot = (targetDocument, anchorElement) => {
5798
+ const root = targetDocument.documentElement;
5799
+ const body = targetDocument.body;
5800
+ const anchorRect = anchorElement?.getBoundingClientRect();
5801
+ return [
5802
+ root.scrollWidth,
5803
+ root.scrollHeight,
5804
+ body?.scrollWidth ?? 0,
5805
+ body?.scrollHeight ?? 0,
5806
+ anchorRect ? Math.round(anchorRect.left) : -1,
5807
+ anchorRect ? Math.round(anchorRect.top) : -1,
5808
+ anchorRect ? Math.round(anchorRect.width) : -1,
5809
+ anchorRect ? Math.round(anchorRect.height) : -1
5810
+ ].join(":");
5811
+ };
5812
+ var waitForRestoreAnchor = async (targetWindow, targetDocument, item, isCurrent) => {
5813
+ const startedAt = targetWindow.performance.now();
5814
+ let previousSnapshot = "";
5815
+ let stableFrameCount = 0;
5816
+ while (isCurrent() && targetWindow.performance.now() - startedAt < RESTORE_WAIT_MAX_MS) {
5817
+ const anchorElement = queryReviewItemAnchorElement(targetDocument, item);
5818
+ const snapshot = getRestoreLayoutSnapshot(targetDocument, anchorElement);
5819
+ const canRestore = item.anchor ? Boolean(anchorElement) : true;
5820
+ if (snapshot === previousSnapshot) {
5821
+ stableFrameCount += 1;
5822
+ } else {
5823
+ stableFrameCount = 0;
5824
+ }
5825
+ if (canRestore && stableFrameCount >= RESTORE_STABLE_FRAME_COUNT) {
5826
+ return anchorElement;
5827
+ }
5828
+ previousSnapshot = snapshot;
5829
+ await waitForNextAnimationFrame(targetWindow);
5830
+ }
5831
+ return queryReviewItemAnchorElement(targetDocument, item);
5832
+ };
4957
5833
  var useReviewItemRestore = ({
4958
5834
  adapter,
4959
5835
  controllerRef,
@@ -4984,14 +5860,20 @@ var useReviewItemRestore = ({
4984
5860
  selectedItemIdRef
4985
5861
  ]);
4986
5862
  const applyItemScroll = useCallback(
4987
- (item) => {
4988
- if (selectedItemIdRef.current !== item.id) return;
5863
+ async (item) => {
5864
+ if (selectedItemIdRef.current !== item.id) return false;
4989
5865
  const targetWindow = iframeRef.current?.contentWindow;
4990
5866
  const targetDocument = iframeRef.current?.contentDocument;
4991
- if (!targetWindow) return;
4992
- const anchorElement = targetDocument ? queryReviewItemAnchorElement(targetDocument, item) : void 0;
4993
- runWithAutoScrollBehavior(targetDocument ?? void 0, () => {
4994
- if (!targetDocument) return;
5867
+ if (!targetWindow || !targetDocument) return false;
5868
+ const isCurrentRestore = () => selectedItemIdRef.current === item.id && iframeRef.current?.contentDocument === targetDocument;
5869
+ const anchorElement = await waitForRestoreAnchor(
5870
+ targetWindow,
5871
+ targetDocument,
5872
+ item,
5873
+ isCurrentRestore
5874
+ );
5875
+ if (!isCurrentRestore()) return false;
5876
+ runWithAutoScrollBehavior(targetDocument, () => {
4995
5877
  setDocumentScrollInstantly(
4996
5878
  targetWindow,
4997
5879
  targetDocument,
@@ -5005,14 +5887,18 @@ var useReviewItemRestore = ({
5005
5887
  });
5006
5888
  onSyncTargetViewport();
5007
5889
  controllerRef.current?.highlightItem(item.id);
5890
+ return true;
5008
5891
  },
5009
5892
  [controllerRef, iframeRef, onSyncTargetViewport, selectedItemIdRef]
5010
5893
  );
5011
5894
  const applyPendingRestore = useCallback(() => {
5012
5895
  const item = pendingRestoreRef.current;
5013
5896
  if (!item) return;
5014
- applyItemScroll(item);
5015
- pendingRestoreRef.current = null;
5897
+ void applyItemScroll(item).then((didApply) => {
5898
+ if (didApply && pendingRestoreRef.current?.id === item.id) {
5899
+ pendingRestoreRef.current = null;
5900
+ }
5901
+ });
5016
5902
  }, [applyItemScroll, pendingRestoreRef]);
5017
5903
  const restoreReviewItem = useCallback(
5018
5904
  (item) => {
@@ -5227,6 +6113,7 @@ var useReviewKitLifecycle = ({
5227
6113
  reviewUserId,
5228
6114
  reviewViewportPresets,
5229
6115
  ruler,
6116
+ adjustmentLabel,
5230
6117
  sizeRef,
5231
6118
  targetRef,
5232
6119
  onApplyPendingRestore,
@@ -5279,6 +6166,7 @@ var useReviewKitLifecycle = ({
5279
6166
  presets: reviewViewportPresets
5280
6167
  },
5281
6168
  ruler,
6169
+ adjustmentLabel,
5282
6170
  onCreateItem,
5283
6171
  onRestoreItem: onRestoreReviewItem,
5284
6172
  onItemsChange: () => {
@@ -5298,8 +6186,7 @@ var useReviewKitLifecycle = ({
5298
6186
  controllerRef.current.setHiddenItemIds(hiddenOverlayItemIdListRef.current);
5299
6187
  onModeChange(controllerRef.current.getMode());
5300
6188
  void onItemsRefresh();
5301
- void onRestoreInitialItem();
5302
- onApplyPendingRestore();
6189
+ void onRestoreInitialItem().then(onApplyPendingRestore);
5303
6190
  onRefreshTargetOverlayState();
5304
6191
  setTargetScrollbarHidden(
5305
6192
  targetDocument,
@@ -5330,6 +6217,7 @@ var useReviewKitLifecycle = ({
5330
6217
  reviewUserId,
5331
6218
  reviewViewportPresets,
5332
6219
  ruler,
6220
+ adjustmentLabel,
5333
6221
  sizeRef,
5334
6222
  targetRef
5335
6223
  ]);
@@ -5535,6 +6423,7 @@ var useReviewController = ({
5535
6423
  reviewUserId,
5536
6424
  reviewViewportPresets,
5537
6425
  ruler,
6426
+ adjustmentLabel,
5538
6427
  selectedItemIdRef,
5539
6428
  size,
5540
6429
  sizeRef,
@@ -5623,6 +6512,7 @@ var useReviewController = ({
5623
6512
  reviewUserId,
5624
6513
  reviewViewportPresets,
5625
6514
  ruler,
6515
+ adjustmentLabel,
5626
6516
  sizeRef,
5627
6517
  targetRef,
5628
6518
  onApplyPendingRestore: applyPendingRestore,
@@ -5653,9 +6543,9 @@ var useReviewController = ({
5653
6543
  import {
5654
6544
  useCallback as useCallback6,
5655
6545
  useEffect as useEffect5,
5656
- useMemo as useMemo2,
6546
+ useMemo as useMemo3,
5657
6547
  useRef,
5658
- useState as useState2
6548
+ useState as useState4
5659
6549
  } from "react";
5660
6550
 
5661
6551
  // src/react-shell/presence/presence.ts
@@ -5872,9 +6762,9 @@ var useReviewPresence = ({
5872
6762
  source
5873
6763
  }) => {
5874
6764
  const presenceSessionRef = useRef(null);
5875
- const [presenceUsers, setPresenceUsers] = useState2([]);
5876
- const [presenceSessionVersion, setPresenceSessionVersion] = useState2(0);
5877
- const presenceSessionId = useMemo2(getReviewPresenceSessionId, []);
6765
+ const [presenceUsers, setPresenceUsers] = useState4([]);
6766
+ const [presenceSessionVersion, setPresenceSessionVersion] = useState4(0);
6767
+ const presenceSessionId = useMemo3(getReviewPresenceSessionId, []);
5878
6768
  const normalizedReviewUserId = reviewUserId.trim();
5879
6769
  const presenceDisplayName = getReviewPresenceDisplayName(
5880
6770
  normalizedReviewUserId
@@ -5882,7 +6772,7 @@ var useReviewPresence = ({
5882
6772
  const presenceColor = getReviewPresenceColor(
5883
6773
  normalizedReviewUserId || presenceSessionId
5884
6774
  );
5885
- const presenceViewport = useMemo2(
6775
+ const presenceViewport = useMemo3(
5886
6776
  () => ({
5887
6777
  label: size.label,
5888
6778
  width: size.width,
@@ -5892,7 +6782,7 @@ var useReviewPresence = ({
5892
6782
  [size]
5893
6783
  );
5894
6784
  const presenceStatus = mode === "idle" ? "reviewing" : "editing";
5895
- const visiblePresenceUsers = useMemo2(
6785
+ const visiblePresenceUsers = useMemo3(
5896
6786
  () => {
5897
6787
  const projectPresenceUsers = presenceUsers.filter(
5898
6788
  (user) => user.projectId === projectId && user.userId.trim()
@@ -5904,14 +6794,14 @@ var useReviewPresence = ({
5904
6794
  },
5905
6795
  [presenceUsers, projectId, reviewPathPrefix]
5906
6796
  );
5907
- const currentPagePresenceUsers = useMemo2(
6797
+ const currentPagePresenceUsers = useMemo3(
5908
6798
  () => visiblePresenceUsers.filter((user) => {
5909
6799
  const userTarget = getPresenceUserTarget(user, reviewPathPrefix);
5910
6800
  return userTarget === activeRoute;
5911
6801
  }),
5912
6802
  [activeRoute, reviewPathPrefix, visiblePresenceUsers]
5913
6803
  );
5914
- const pagePresenceUsers = useMemo2(() => {
6804
+ const pagePresenceUsers = useMemo3(() => {
5915
6805
  const usersByTarget = /* @__PURE__ */ new Map();
5916
6806
  visiblePresenceUsers.forEach((user) => {
5917
6807
  const userTarget = getPresenceUserTarget(user, reviewPathPrefix);
@@ -6024,16 +6914,16 @@ var useReviewPresence = ({
6024
6914
  import {
6025
6915
  useCallback as useCallback8,
6026
6916
  useEffect as useEffect7,
6027
- useState as useState4
6917
+ useState as useState6
6028
6918
  } from "react";
6029
6919
 
6030
6920
  // src/react-shell/hooks/use.review.ruler.drag.ts
6031
6921
  import {
6032
6922
  useCallback as useCallback7,
6033
6923
  useEffect as useEffect6,
6034
- useMemo as useMemo3,
6924
+ useMemo as useMemo4,
6035
6925
  useRef as useRef2,
6036
- useState as useState3
6926
+ useState as useState5
6037
6927
  } from "react";
6038
6928
 
6039
6929
  // src/react-shell/ruler/ruler.ts
@@ -6067,11 +6957,11 @@ var useReviewRulerDrag = ({
6067
6957
  const rulerDragRectRef = useRef2(null);
6068
6958
  const isRulerDraggingRef = useRef2(false);
6069
6959
  const sizeRef = useRef2(size);
6070
- const [rulerStart, setRulerStart] = useState3(null);
6071
- const [rulerPoint, setRulerPoint] = useState3(null);
6072
- const [rulerHover, setRulerHover] = useState3(null);
6073
- const [isRulerDragging, setIsRulerDragging] = useState3(false);
6074
- const rulerMeasure = useMemo3(
6960
+ const [rulerStart, setRulerStart] = useState5(null);
6961
+ const [rulerPoint, setRulerPoint] = useState5(null);
6962
+ const [rulerHover, setRulerHover] = useState5(null);
6963
+ const [isRulerDragging, setIsRulerDragging] = useState5(false);
6964
+ const rulerMeasure = useMemo4(
6075
6965
  () => getRulerMeasure(rulerStart, rulerPoint),
6076
6966
  [rulerPoint, rulerStart]
6077
6967
  );
@@ -6240,7 +7130,7 @@ var useReviewRuler = ({
6240
7130
  onCancelReviewMode,
6241
7131
  onCloseTransientPanels
6242
7132
  }) => {
6243
- const [isRulerVisible, setIsRulerVisible] = useState4(false);
7133
+ const [isRulerVisible, setIsRulerVisible] = useState6(false);
6244
7134
  const isRulerAvailable = ruler?.enabled !== false && typeof size.designWidth === "number" && size.designWidth > 0;
6245
7135
  const rulerUnit = ruler?.unit ?? "px";
6246
7136
  const rulerScaleX = isRulerAvailable && size.designWidth ? size.width / size.designWidth : 1;
@@ -6300,25 +7190,25 @@ var useReviewRuler = ({
6300
7190
  };
6301
7191
 
6302
7192
  // src/react-shell/hooks/use.review.settings.ts
6303
- import { useCallback as useCallback9, useEffect as useEffect8, useState as useState5 } from "react";
7193
+ import { useCallback as useCallback9, useEffect as useEffect8, useState as useState7 } from "react";
6304
7194
  var useReviewSettings = ({
6305
7195
  onCancelReviewMode,
6306
7196
  onCloseInitialPrompt,
6307
7197
  onCloseSitemap,
6308
7198
  onReloadTargetFrame
6309
7199
  }) => {
6310
- const [figmaTokenDraft, setFigmaTokenDraft] = useState5(getStoredFigmaToken);
6311
- const [reviewUserId, setReviewUserId] = useState5(getStoredReviewUserId);
6312
- const [reviewUserIdDraft, setReviewUserIdDraft] = useState5(
7200
+ const [figmaTokenDraft, setFigmaTokenDraft] = useState7(getStoredFigmaToken);
7201
+ const [reviewUserId, setReviewUserId] = useState7(getStoredReviewUserId);
7202
+ const [reviewUserIdDraft, setReviewUserIdDraft] = useState7(
6313
7203
  getStoredReviewUserId
6314
7204
  );
6315
- const [reviewTheme, setReviewTheme] = useState5(getStoredReviewTheme);
6316
- const [reviewThemeDraft, setReviewThemeDraft] = useState5(getStoredReviewTheme);
6317
- const [systemReviewTheme, setSystemReviewTheme] = useState5(getSystemReviewTheme);
6318
- const [figmaSettingsStatus, setFigmaSettingsStatus] = useState5("");
6319
- const [isFigmaSettingsOpen, setIsFigmaSettingsOpen] = useState5(false);
6320
- const [isFigmaTokenVisible, setIsFigmaTokenVisible] = useState5(false);
6321
- const [isFigmaTokenGuideOpen, setIsFigmaTokenGuideOpen] = useState5(false);
7205
+ const [reviewTheme, setReviewTheme] = useState7(getStoredReviewTheme);
7206
+ const [reviewThemeDraft, setReviewThemeDraft] = useState7(getStoredReviewTheme);
7207
+ const [systemReviewTheme, setSystemReviewTheme] = useState7(getSystemReviewTheme);
7208
+ const [figmaSettingsStatus, setFigmaSettingsStatus] = useState7("");
7209
+ const [isFigmaSettingsOpen, setIsFigmaSettingsOpen] = useState7(false);
7210
+ const [isFigmaTokenVisible, setIsFigmaTokenVisible] = useState7(false);
7211
+ const [isFigmaTokenGuideOpen, setIsFigmaTokenGuideOpen] = useState7(false);
6322
7212
  const effectiveReviewTheme = reviewTheme === "system" ? systemReviewTheme : reviewTheme;
6323
7213
  const closeFigmaSettings = useCallback9(() => {
6324
7214
  setIsFigmaSettingsOpen(false);
@@ -6420,13 +7310,12 @@ var useReviewSettings = ({
6420
7310
  };
6421
7311
 
6422
7312
  // src/react-shell/hooks/use.review.shell.data.ts
6423
- import { useCallback as useCallback10, useMemo as useMemo4, useState as useState6 } from "react";
6424
- var createEmptySitemapQaCount = () => ({
6425
- local: 0,
6426
- remote: 0
6427
- });
7313
+ import { useCallback as useCallback10, useMemo as useMemo5, useState as useState8 } from "react";
7314
+ var SITEMAP_STATUS_DONE = "done";
6428
7315
  var useReviewShellData = ({
6429
7316
  activeRoute,
7317
+ isAllQaVisible,
7318
+ isRemoteSource,
6430
7319
  pages,
6431
7320
  reviewPathPrefix,
6432
7321
  reviewViewportPresets,
@@ -6435,59 +7324,112 @@ var useReviewShellData = ({
6435
7324
  target,
6436
7325
  viewportPresets
6437
7326
  }) => {
6438
- const [items, setItems] = useState6([]);
6439
- const [hiddenOverlayItemIds, setHiddenOverlayItemIds] = useState6(
7327
+ const [items, setItems] = useState8([]);
7328
+ const [hiddenOverlayItemIds, setHiddenOverlayItemIds] = useState8(
6440
7329
  () => /* @__PURE__ */ new Set()
6441
7330
  );
6442
- const [qaFilter, setQaFilter] = useState6("all");
6443
- const [sitemapItems, setSitemapItems] = useState6(() => ({
7331
+ const [qaFilter, setQaFilter] = useState8("all");
7332
+ const [qaStatusFilter, setQaStatusFilter] = useState8("all");
7333
+ const [sitemapItems, setSitemapItems] = useState8(() => ({
6444
7334
  local: [],
6445
7335
  remote: []
6446
7336
  }));
6447
- const targetSrc = useMemo4(() => buildTargetSrc(target), [target]);
6448
- const pageTargets = useMemo4(
7337
+ const targetSrc = useMemo5(() => buildTargetSrc(target), [target]);
7338
+ const pageTargets = useMemo5(
6449
7339
  () => new Set(
6450
7340
  pages.map((page) => normalizeTarget(page.href, reviewPathPrefix))
6451
7341
  ),
6452
7342
  [pages, reviewPathPrefix]
6453
7343
  );
6454
- const activeItems = useMemo4(
6455
- () => items.filter((item) => getItemTarget(item, reviewPathPrefix) === activeRoute).sort((a, b) => b.createdAt.localeCompare(a.createdAt)),
6456
- [activeRoute, items, reviewPathPrefix]
7344
+ const sitemapSourceItems = useMemo5(
7345
+ () => isRemoteSource ? sitemapItems.remote : sitemapItems.local,
7346
+ [isRemoteSource, sitemapItems]
6457
7347
  );
6458
- const numberedActiveItems = useMemo4(
7348
+ const activeItems = useMemo5(
7349
+ () => {
7350
+ const sourceItems = isAllQaVisible ? sitemapSourceItems : items.filter(
7351
+ (item) => getItemTarget(item, reviewPathPrefix) === activeRoute
7352
+ );
7353
+ return [...sourceItems].sort(
7354
+ (a, b) => b.createdAt.localeCompare(a.createdAt)
7355
+ );
7356
+ },
7357
+ [activeRoute, isAllQaVisible, items, reviewPathPrefix, sitemapSourceItems]
7358
+ );
7359
+ const numberedActiveItems = useMemo5(
6459
7360
  () => getNumberedReviewItems(activeItems, reviewViewportPresets),
6460
7361
  [activeItems, reviewViewportPresets]
6461
7362
  );
6462
- const filteredNumberedActiveItems = useMemo4(
7363
+ const scopeFilteredNumberedActiveItems = useMemo5(
6463
7364
  () => qaFilter === "all" ? numberedActiveItems : numberedActiveItems.filter(
6464
7365
  (numberedItem) => numberedItem.scope === qaFilter
6465
7366
  ),
6466
7367
  [numberedActiveItems, qaFilter]
6467
7368
  );
6468
- const hiddenOverlayItemIdList = useMemo4(
7369
+ const statusFilteredNumberedActiveItems = useMemo5(
7370
+ () => qaStatusFilter === "all" ? numberedActiveItems : numberedActiveItems.filter(
7371
+ (numberedItem) => normalizeReviewItemStatus(numberedItem.item.status) === qaStatusFilter
7372
+ ),
7373
+ [numberedActiveItems, qaStatusFilter]
7374
+ );
7375
+ const filteredNumberedActiveItems = useMemo5(
7376
+ () => qaStatusFilter === "all" ? scopeFilteredNumberedActiveItems : scopeFilteredNumberedActiveItems.filter(
7377
+ (numberedItem) => normalizeReviewItemStatus(numberedItem.item.status) === qaStatusFilter
7378
+ ),
7379
+ [qaStatusFilter, scopeFilteredNumberedActiveItems]
7380
+ );
7381
+ const hiddenOverlayItemIdList = useMemo5(
6469
7382
  () => Array.from(hiddenOverlayItemIds),
6470
7383
  [hiddenOverlayItemIds]
6471
7384
  );
6472
- const qaFilterCounts = useMemo4(() => {
7385
+ const qaFilterCounts = useMemo5(() => {
6473
7386
  const counts = /* @__PURE__ */ new Map();
6474
- counts.set("all", numberedActiveItems.length);
6475
- numberedActiveItems.forEach((numberedItem) => {
7387
+ counts.set("all", statusFilteredNumberedActiveItems.length);
7388
+ statusFilteredNumberedActiveItems.forEach((numberedItem) => {
6476
7389
  counts.set(numberedItem.scope, (counts.get(numberedItem.scope) ?? 0) + 1);
6477
7390
  });
6478
7391
  return counts;
6479
- }, [numberedActiveItems]);
6480
- const getItemPresetScope = useCallback10(
6481
- (item) => getViewportPresetKind(
6482
- findViewportPreset(
6483
- viewportPresets,
6484
- item.viewport?.width ?? 0,
6485
- item.viewport?.height ?? 0
6486
- )
7392
+ }, [statusFilteredNumberedActiveItems]);
7393
+ const qaStatusFilterCounts = useMemo5(() => {
7394
+ const counts = /* @__PURE__ */ new Map();
7395
+ counts.set("all", scopeFilteredNumberedActiveItems.length);
7396
+ scopeFilteredNumberedActiveItems.forEach((numberedItem) => {
7397
+ const status = normalizeReviewItemStatus(numberedItem.item.status);
7398
+ counts.set(status, (counts.get(status) ?? 0) + 1);
7399
+ });
7400
+ return counts;
7401
+ }, [scopeFilteredNumberedActiveItems]);
7402
+ const getItemPreset = useCallback10(
7403
+ (item) => findViewportPreset(
7404
+ viewportPresets,
7405
+ item.viewport?.width ?? 0,
7406
+ item.viewport?.height ?? 0
6487
7407
  ),
6488
7408
  [viewportPresets]
6489
7409
  );
6490
- const presetScopeCounts = useMemo4(() => {
7410
+ const getItemPresetScope = useCallback10(
7411
+ (item) => getViewportPresetKind(getItemPreset(item)),
7412
+ [getItemPreset]
7413
+ );
7414
+ const getItemPresetColumn = useCallback10(
7415
+ (item) => {
7416
+ const preset = getItemPreset(item);
7417
+ const presetIndex = Math.max(0, viewportPresets.indexOf(preset));
7418
+ return createSitemapViewportColumn(preset, presetIndex);
7419
+ },
7420
+ [getItemPreset, viewportPresets]
7421
+ );
7422
+ const getItemCountScope = useCallback10(
7423
+ (item) => item.scope === "dom" ? "dom" : getItemPresetScope(item),
7424
+ [getItemPresetScope]
7425
+ );
7426
+ const activeRemainingItemCount = useMemo5(
7427
+ () => activeItems.filter(
7428
+ (item) => normalizeReviewItemStatus(item.status) !== SITEMAP_STATUS_DONE
7429
+ ).length,
7430
+ [activeItems]
7431
+ );
7432
+ const presetScopeCounts = useMemo5(() => {
6491
7433
  const counts = /* @__PURE__ */ new Map();
6492
7434
  activeItems.forEach((item) => {
6493
7435
  const scope = getItemPresetScope(item);
@@ -6496,7 +7438,7 @@ var useReviewShellData = ({
6496
7438
  return counts;
6497
7439
  }, [activeItems, getItemPresetScope]);
6498
7440
  const currentPresetScope = getViewportPresetKind(size);
6499
- const pageQaCounts = useMemo4(() => {
7441
+ const pageQaCounts = useMemo5(() => {
6500
7442
  const counts = /* @__PURE__ */ new Map();
6501
7443
  const addItems = (sourceKey, sourceItems) => {
6502
7444
  sourceItems.forEach((item) => {
@@ -6504,16 +7446,48 @@ var useReviewShellData = ({
6504
7446
  getItemTarget(item, reviewPathPrefix),
6505
7447
  reviewPathPrefix
6506
7448
  );
6507
- const count = counts.get(pageTarget) ?? createEmptySitemapQaCount();
6508
- count[sourceKey] += 1;
6509
- counts.set(pageTarget, count);
7449
+ const currentCount = counts.get(pageTarget) ?? createEmptySitemapQaCount();
7450
+ const status = normalizeReviewItemStatus(item.status);
7451
+ const scope = getItemCountScope(item);
7452
+ const viewportColumn = getItemPresetColumn(item);
7453
+ const currentViewportCount = currentCount.viewport[viewportColumn.key] ?? { total: 0, remaining: 0 };
7454
+ const isRemaining = status !== SITEMAP_STATUS_DONE;
7455
+ counts.set(pageTarget, {
7456
+ ...currentCount,
7457
+ total: currentCount.total + 1,
7458
+ remaining: isRemaining ? currentCount.remaining + 1 : currentCount.remaining,
7459
+ local: currentCount.local + (sourceKey === "local" ? 1 : 0),
7460
+ remote: currentCount.remote + (sourceKey === "remote" ? 1 : 0),
7461
+ status: {
7462
+ ...currentCount.status,
7463
+ [status]: currentCount.status[status] + 1
7464
+ },
7465
+ scope: {
7466
+ ...currentCount.scope,
7467
+ [scope]: (currentCount.scope[scope] ?? 0) + 1
7468
+ },
7469
+ viewport: {
7470
+ ...currentCount.viewport,
7471
+ [viewportColumn.key]: {
7472
+ total: currentViewportCount.total + 1,
7473
+ remaining: isRemaining ? currentViewportCount.remaining + 1 : currentViewportCount.remaining
7474
+ }
7475
+ }
7476
+ });
6510
7477
  });
6511
7478
  };
6512
7479
  addItems("local", sitemapItems.local);
6513
7480
  addItems("remote", sitemapItems.remote);
6514
7481
  return counts;
6515
- }, [reviewPathPrefix, sitemapItems]);
6516
- const selectedNumberedItem = useMemo4(
7482
+ }, [getItemCountScope, getItemPresetColumn, reviewPathPrefix, sitemapItems]);
7483
+ const allQaCount = useMemo5(
7484
+ () => Array.from(pageQaCounts.values()).reduce(
7485
+ addSitemapQaCounts,
7486
+ createEmptySitemapQaCount()
7487
+ ),
7488
+ [pageQaCounts]
7489
+ );
7490
+ const selectedNumberedItem = useMemo5(
6517
7491
  () => selectedItemId ? numberedActiveItems.find(
6518
7492
  (numberedItem) => numberedItem.item.id === selectedItemId
6519
7493
  ) : void 0,
@@ -6521,6 +7495,8 @@ var useReviewShellData = ({
6521
7495
  );
6522
7496
  return {
6523
7497
  activeItems,
7498
+ activeRemainingItemCount,
7499
+ allQaCount,
6524
7500
  currentPresetScope,
6525
7501
  filteredNumberedActiveItems,
6526
7502
  getItemPresetScope,
@@ -6531,10 +7507,13 @@ var useReviewShellData = ({
6531
7507
  presetScopeCounts,
6532
7508
  qaFilter,
6533
7509
  qaFilterCounts,
7510
+ qaStatusFilter,
7511
+ qaStatusFilterCounts,
6534
7512
  selectedNumberedItem,
6535
7513
  setHiddenOverlayItemIds,
6536
7514
  setItems,
6537
7515
  setQaFilter,
7516
+ setQaStatusFilter,
6538
7517
  setSitemapItems,
6539
7518
  targetSrc
6540
7519
  };
@@ -6635,9 +7614,9 @@ var useReviewShellHotkeys = ({
6635
7614
 
6636
7615
  // src/react-shell/hooks/use.review.shell.state.ts
6637
7616
  import {
6638
- useMemo as useMemo5,
7617
+ useMemo as useMemo6,
6639
7618
  useRef as useRef3,
6640
- useState as useState7
7619
+ useState as useState9
6641
7620
  } from "react";
6642
7621
 
6643
7622
  // src/react-shell/adapters.ts
@@ -6782,11 +7761,11 @@ var useReviewShellState = ({
6782
7761
  reviewPathPrefix
6783
7762
  }) => {
6784
7763
  const viewportPresets = presets.length > 0 ? presets : DEFAULT_REVIEW_VIEWPORT_PRESETS;
6785
- const reviewViewportPresets = useMemo5(
7764
+ const reviewViewportPresets = useMemo6(
6786
7765
  () => toReviewViewportPresets(viewportPresets),
6787
7766
  [viewportPresets]
6788
7767
  );
6789
- const normalizedAdapters = useMemo5(
7768
+ const normalizedAdapters = useMemo6(
6790
7769
  () => normalizeReviewShellAdapters(adapters),
6791
7770
  [adapters]
6792
7771
  );
@@ -6794,7 +7773,7 @@ var useReviewShellState = ({
6794
7773
  const remoteAdapterEntry = normalizedAdapters.remote;
6795
7774
  const sourceEntries = normalizedAdapters.sources;
6796
7775
  const defaultSource = sourceEntries[0]?.label ?? "local";
6797
- const [source, setSource] = useState7(() => {
7776
+ const [source, setSource] = useState9(() => {
6798
7777
  const initialSource = getInitialSource(remoteAdapterEntry?.label);
6799
7778
  return sourceEntries.some((entry) => entry.label === initialSource) ? initialSource : defaultSource;
6800
7779
  });
@@ -6815,30 +7794,30 @@ var useReviewShellState = ({
6815
7794
  const pendingInitialItemIdRef = useRef3(getInitialItemId());
6816
7795
  const selectedItemIdRef = useRef3(getInitialItemId());
6817
7796
  const hiddenOverlayItemIdListRef = useRef3([]);
6818
- const [target, setTarget] = useState7(
7797
+ const [target, setTarget] = useState9(
6819
7798
  () => getInitialTarget(reviewPathPrefix)
6820
7799
  );
6821
- const [draftTarget, setDraftTarget] = useState7(
7800
+ const [draftTarget, setDraftTarget] = useState9(
6822
7801
  () => getInitialTarget(reviewPathPrefix)
6823
7802
  );
6824
- const [activeRoute, setActiveRoute] = useState7(
7803
+ const [activeRoute, setActiveRoute] = useState9(
6825
7804
  () => getInitialTarget(reviewPathPrefix)
6826
7805
  );
6827
- const [size, setSize] = useState7(
7806
+ const [size, setSize] = useState9(
6828
7807
  () => getInitialSize(viewportPresets)
6829
7808
  );
6830
- const [mode, setMode] = useState7("idle");
6831
- const [targetOverlayState, setTargetOverlayState] = useState7({
7809
+ const [mode, setMode] = useState9("idle");
7810
+ const [targetOverlayState, setTargetOverlayState] = useState9({
6832
7811
  grid: false,
6833
7812
  figma: false
6834
7813
  });
6835
- const [selectedItemId, setSelectedItemId] = useState7(getInitialItemId());
6836
- const [isListVisible, setIsListVisible] = useState7(true);
6837
- const [isSitemapOpen, setIsSitemapOpen] = useState7(false);
6838
- const [isInitialPromptOpen, setIsInitialPromptOpen] = useState7(false);
6839
- const [copyLabel, setCopyLabel] = useState7("Copy URL");
6840
- const [toastMessage, setToastMessage] = useState7("");
6841
- const [copiedPromptKey, setCopiedPromptKey] = useState7(null);
7814
+ const [selectedItemId, setSelectedItemId] = useState9(getInitialItemId());
7815
+ const [isListVisible, setIsListVisible] = useState9(true);
7816
+ const [isSitemapOpen, setIsSitemapOpen] = useState9(false);
7817
+ const [isInitialPromptOpen, setIsInitialPromptOpen] = useState9(false);
7818
+ const [copyLabel, setCopyLabel] = useState9("Copy URL");
7819
+ const [toastMessage, setToastMessage] = useState9("");
7820
+ const [copiedPromptKey, setCopiedPromptKey] = useState9(null);
6842
7821
  const targetRef = useRef3(target);
6843
7822
  const sizeRef = useRef3(size);
6844
7823
  const isFigmaOverlayAvailable = getIsFigmaOverlayAvailable(size);
@@ -7130,12 +8109,15 @@ var removeReviewItem = async ({
7130
8109
  };
7131
8110
 
7132
8111
  // src/react-shell/review/shell.tsx
7133
- import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
8112
+ import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
7134
8113
  var getReviewModeWriteMode = (mode) => {
7135
8114
  if (mode === "element") return "dom";
7136
8115
  if (mode === "note" || mode === "area") return mode;
7137
8116
  return null;
7138
8117
  };
8118
+ var SOURCE_PANEL_MAX_WIDTH = 440;
8119
+ var SOURCE_PANEL_MIN_WIDTH = 240;
8120
+ var SOURCE_PANEL_MAX_HEIGHT = 260;
7139
8121
  var ReviewShell = ({
7140
8122
  projectId,
7141
8123
  pages,
@@ -7143,8 +8125,10 @@ var ReviewShell = ({
7143
8125
  presets = DEFAULT_REVIEW_VIEWPORT_PRESETS,
7144
8126
  ruler,
7145
8127
  initialPrompt = DEFAULT_INITIAL_REVIEW_PROMPT,
8128
+ adjustmentLabel,
7146
8129
  reviewPathPrefix = DEFAULT_REVIEW_PATH_PREFIX,
7147
8130
  sourceRoot,
8131
+ sourceInspector,
7148
8132
  presence
7149
8133
  }) => {
7150
8134
  const {
@@ -7161,7 +8145,7 @@ var ReviewShell = ({
7161
8145
  frameScrollRef,
7162
8146
  hiddenOverlayItemIdListRef,
7163
8147
  iframeRef,
7164
- isFigmaOverlayAvailable,
8148
+ isFigmaOverlayAvailable: isViewportFigmaOverlayAvailable,
7165
8149
  isInitialPromptOpen,
7166
8150
  isListVisible,
7167
8151
  isRemoteSource,
@@ -7204,8 +8188,21 @@ var ReviewShell = ({
7204
8188
  reviewPathPrefix
7205
8189
  });
7206
8190
  const sourceShortcutCleanupRef = useRef4(null);
8191
+ const sourceInspectorInteractionRef = useRef4(false);
8192
+ const [sourceInspectorState, setSourceInspectorState] = useState10(null);
8193
+ const [isAllQaVisible, setIsAllQaVisible] = useState10(false);
8194
+ const sourceOpenOptions = useMemo7(
8195
+ () => ({
8196
+ ...sourceInspector,
8197
+ sourceRoot
8198
+ }),
8199
+ [sourceInspector, sourceRoot]
8200
+ );
8201
+ const isSourceInspectorEnabled = sourceInspector?.enabled !== false;
7207
8202
  const {
7208
8203
  activeItems,
8204
+ activeRemainingItemCount,
8205
+ allQaCount,
7209
8206
  currentPresetScope,
7210
8207
  filteredNumberedActiveItems,
7211
8208
  getItemPresetScope,
@@ -7216,14 +8213,19 @@ var ReviewShell = ({
7216
8213
  presetScopeCounts,
7217
8214
  qaFilter,
7218
8215
  qaFilterCounts,
8216
+ qaStatusFilter,
8217
+ qaStatusFilterCounts,
7219
8218
  selectedNumberedItem,
7220
8219
  setHiddenOverlayItemIds,
7221
8220
  setItems,
7222
8221
  setQaFilter,
8222
+ setQaStatusFilter,
7223
8223
  setSitemapItems,
7224
8224
  targetSrc
7225
8225
  } = useReviewShellData({
7226
8226
  activeRoute,
8227
+ isAllQaVisible,
8228
+ isRemoteSource,
7227
8229
  pages,
7228
8230
  reviewPathPrefix,
7229
8231
  reviewViewportPresets,
@@ -7232,7 +8234,14 @@ var ReviewShell = ({
7232
8234
  target,
7233
8235
  viewportPresets
7234
8236
  });
7235
- const [editingItem, setEditingItem] = useState8(null);
8237
+ const [targetFigmaState, setTargetFigmaState] = useState10(null);
8238
+ const targetFigmaConfig = targetFigmaState?.targetSrc === targetSrc ? targetFigmaState.config : null;
8239
+ const figmaFrameUrl = useMemo7(
8240
+ () => getFigmaFrameUrl(targetFigmaConfig, size),
8241
+ [targetFigmaConfig, size]
8242
+ );
8243
+ const isFigmaOverlayAvailable = isViewportFigmaOverlayAvailable && Boolean(targetFigmaConfig);
8244
+ const [editingItem, setEditingItem] = useState10(null);
7236
8245
  const initialPromptText = initialPrompt.trim();
7237
8246
  const refreshItems = useCallback11(
7238
8247
  () => refreshReviewItems({
@@ -7365,6 +8374,7 @@ var ReviewShell = ({
7365
8374
  reviewUserId,
7366
8375
  reviewViewportPresets,
7367
8376
  ruler,
8377
+ adjustmentLabel,
7368
8378
  selectedItemIdRef,
7369
8379
  size,
7370
8380
  sizeRef,
@@ -7430,18 +8440,39 @@ var ReviewShell = ({
7430
8440
  window.clearTimeout(transitionTimeout);
7431
8441
  };
7432
8442
  }, [isListVisible, size.height, size.width, syncTargetViewport, targetSrc]);
7433
- const applyTarget = () => {
7434
- const normalizedTarget = normalizeTarget(draftTarget, reviewPathPrefix);
8443
+ const applyTarget = async () => {
8444
+ const parsedInput = parseReviewAddressInput(draftTarget, reviewPathPrefix);
8445
+ const normalizedTarget = parsedInput.target;
8446
+ const nextSource = parsedInput.source && sourceEntries.some((entry) => entry.label === parsedInput.source) ? parsedInput.source : source;
8447
+ const nextSize = parsedInput.width && parsedInput.height ? findViewportPreset(
8448
+ viewportPresets,
8449
+ parsedInput.width,
8450
+ parsedInput.height
8451
+ ) : sizeRef.current;
8452
+ const nextAdapter = sourceEntries.find((entry) => entry.label === nextSource) ?? activeAdapterEntry;
8453
+ if (parsedInput.itemId) {
8454
+ const item = await nextAdapter.adapter.get(parsedInput.itemId);
8455
+ if (item) {
8456
+ setIsAllQaVisible(false);
8457
+ setSource(nextSource);
8458
+ restoreReviewItem(item);
8459
+ return;
8460
+ }
8461
+ }
7435
8462
  clearSelectedItem();
8463
+ setIsAllQaVisible(false);
8464
+ setSource(nextSource);
7436
8465
  targetRef.current = normalizedTarget;
7437
8466
  setActiveRoute(normalizedTarget);
7438
8467
  setDraftTarget(normalizedTarget);
8468
+ setSize(nextSize);
7439
8469
  setTarget(normalizedTarget);
7440
- updateShellUrl(normalizedTarget, sizeRef.current, source);
8470
+ updateShellUrl(normalizedTarget, nextSize, nextSource);
7441
8471
  };
7442
8472
  const selectPage = (href) => {
7443
8473
  const normalizedTarget = normalizeTarget(href, reviewPathPrefix);
7444
8474
  clearSelectedItem();
8475
+ setIsAllQaVisible(false);
7445
8476
  targetRef.current = normalizedTarget;
7446
8477
  setActiveRoute(normalizedTarget);
7447
8478
  setDraftTarget(normalizedTarget);
@@ -7449,6 +8480,10 @@ var ReviewShell = ({
7449
8480
  updateShellUrl(normalizedTarget, sizeRef.current, source);
7450
8481
  setIsSitemapOpen(false);
7451
8482
  };
8483
+ const selectAllQa = () => {
8484
+ setIsAllQaVisible(true);
8485
+ setIsSitemapOpen(false);
8486
+ };
7452
8487
  const setReviewMode = (nextMode) => {
7453
8488
  const writeMode = getReviewModeWriteMode(nextMode);
7454
8489
  if (writeMode && !activeAdapterEntry.writeModes.includes(writeMode)) return;
@@ -7486,6 +8521,132 @@ var ReviewShell = ({
7486
8521
  },
7487
8522
  [setToastMessage]
7488
8523
  );
8524
+ const refreshTargetFigmaConfig = useCallback11(() => {
8525
+ const config = getTargetFigmaFrameConfig(
8526
+ iframeRef.current?.contentWindow
8527
+ );
8528
+ setTargetFigmaState(config ? { targetSrc, config } : null);
8529
+ }, [iframeRef, targetSrc]);
8530
+ const clearSourceInspector = useCallback11(() => {
8531
+ sourceInspectorInteractionRef.current = false;
8532
+ setSourceInspectorState(null);
8533
+ }, []);
8534
+ const getSourceInspectorRect = useCallback11(
8535
+ (element) => {
8536
+ const frame = iframeRef.current;
8537
+ if (!frame) return null;
8538
+ const frameRect = frame.getBoundingClientRect();
8539
+ const elementRect = element.getBoundingClientRect();
8540
+ const left = Math.max(frameRect.left, frameRect.left + elementRect.left);
8541
+ const top = Math.max(frameRect.top, frameRect.top + elementRect.top);
8542
+ const right = Math.min(
8543
+ frameRect.right,
8544
+ frameRect.left + elementRect.right
8545
+ );
8546
+ const bottom = Math.min(
8547
+ frameRect.bottom,
8548
+ frameRect.top + elementRect.bottom
8549
+ );
8550
+ return {
8551
+ height: Math.max(2, bottom - top),
8552
+ left,
8553
+ top,
8554
+ width: Math.max(2, right - left)
8555
+ };
8556
+ },
8557
+ [iframeRef]
8558
+ );
8559
+ const getSourceInspectorPanelPosition = useCallback11(
8560
+ (rect) => {
8561
+ const margin = 12;
8562
+ const gap = 10;
8563
+ const preferredLeft = rect.left + rect.width + gap;
8564
+ const rightSpace = window.innerWidth - preferredLeft - margin;
8565
+ const leftSpace = rect.left - gap - margin;
8566
+ const canOpenRight = rightSpace >= SOURCE_PANEL_MIN_WIDTH;
8567
+ const canOpenLeft = leftSpace >= SOURCE_PANEL_MIN_WIDTH;
8568
+ const left = canOpenRight || !canOpenLeft ? preferredLeft : margin;
8569
+ const right = canOpenRight || !canOpenLeft ? null : Math.max(margin, window.innerWidth - (rect.left - gap));
8570
+ const maxWidth = Math.min(
8571
+ SOURCE_PANEL_MAX_WIDTH,
8572
+ Math.max(
8573
+ SOURCE_PANEL_MIN_WIDTH,
8574
+ canOpenRight ? rightSpace : canOpenLeft ? leftSpace : window.innerWidth - margin * 2
8575
+ )
8576
+ );
8577
+ const top = Math.min(
8578
+ Math.max(margin, rect.top),
8579
+ Math.max(margin, window.innerHeight - SOURCE_PANEL_MAX_HEIGHT - margin)
8580
+ );
8581
+ return { left, maxWidth, right, top };
8582
+ },
8583
+ []
8584
+ );
8585
+ const showSourceInspectorForTarget = useCallback11(
8586
+ (target2, isPinned = false) => {
8587
+ const candidates = getSourceCandidates(target2).map((candidate) => ({
8588
+ ...candidate,
8589
+ openUrl: getSourceOpenUrl(candidate.source, {
8590
+ ...sourceOpenOptions,
8591
+ omitPosition: !candidate.usesPosition
8592
+ })
8593
+ }));
8594
+ const firstCandidate = candidates[0];
8595
+ const rect = firstCandidate ? getSourceInspectorRect(firstCandidate.element) : null;
8596
+ if (!firstCandidate || !rect) {
8597
+ setSourceInspectorState(null);
8598
+ return [];
8599
+ }
8600
+ const { left, maxWidth, right, top } = getSourceInspectorPanelPosition(rect);
8601
+ setSourceInspectorState({
8602
+ candidates,
8603
+ isPinned,
8604
+ panelLeft: left,
8605
+ panelMaxWidth: maxWidth,
8606
+ panelRight: right,
8607
+ panelTop: top,
8608
+ rect
8609
+ });
8610
+ return candidates;
8611
+ },
8612
+ [
8613
+ getSourceInspectorPanelPosition,
8614
+ getSourceInspectorRect,
8615
+ sourceOpenOptions
8616
+ ]
8617
+ );
8618
+ const showSourceOutlineForTarget = useCallback11(
8619
+ (target2) => {
8620
+ const firstCandidate = getSourceCandidates(target2)[0];
8621
+ const rect = firstCandidate ? getSourceInspectorRect(firstCandidate.element) : null;
8622
+ if (!firstCandidate || !rect) {
8623
+ setSourceInspectorState(null);
8624
+ return null;
8625
+ }
8626
+ setSourceInspectorState({
8627
+ candidates: [],
8628
+ isPinned: false,
8629
+ panelLeft: 0,
8630
+ panelMaxWidth: SOURCE_PANEL_MAX_WIDTH,
8631
+ panelRight: null,
8632
+ panelTop: 0,
8633
+ rect
8634
+ });
8635
+ return firstCandidate;
8636
+ },
8637
+ [getSourceInspectorRect]
8638
+ );
8639
+ const openSourceCandidate = useCallback11(
8640
+ (candidate) => {
8641
+ const didOpen = openSourceInEditor(candidate.source, {
8642
+ ...sourceOpenOptions,
8643
+ omitPosition: !candidate.usesPosition
8644
+ });
8645
+ showToast(didOpen ? "Source opened" : "Source root required");
8646
+ clearSourceInspector();
8647
+ },
8648
+ [clearSourceInspector, showToast, sourceOpenOptions]
8649
+ );
7489
8650
  const cleanupSourceOpenShortcut = useCallback11(() => {
7490
8651
  sourceShortcutCleanupRef.current?.();
7491
8652
  sourceShortcutCleanupRef.current = null;
@@ -7498,8 +8659,10 @@ var ReviewShell = ({
7498
8659
  } catch {
7499
8660
  return;
7500
8661
  }
7501
- if (!frameDocument) return;
7502
- const hoverAttribute = "data-dfwr-source-hover";
8662
+ if (!frameDocument || !isSourceInspectorEnabled) return;
8663
+ const frameRoot = frameDocument.head ?? frameDocument.documentElement;
8664
+ const frameBody = frameDocument.body ?? frameDocument.documentElement;
8665
+ if (!frameRoot || !frameBody) return;
7503
8666
  const optionAttribute = "data-dfwr-source-option";
7504
8667
  const fontOverlayAttribute = "data-dfwr-source-fonts";
7505
8668
  const style = frameDocument.createElement("style");
@@ -7528,17 +8691,13 @@ var ReviewShell = ({
7528
8691
  pointer-events: none !important;
7529
8692
  }
7530
8693
 
7531
- [${hoverAttribute}="true"] {
7532
- outline: 2px solid rgba(124, 199, 255, 0.96) !important;
7533
- outline-offset: 2px !important;
7534
- }
7535
-
7536
8694
  [${fontOverlayAttribute}] {
7537
8695
  position: fixed !important;
7538
8696
  z-index: 2147483647 !important;
7539
8697
  display: flex !important;
7540
8698
  flex-direction: column !important;
7541
- max-width: 180px !important;
8699
+ width: max-content !important;
8700
+ max-width: calc(100vw - 8px) !important;
7542
8701
  border: 1px solid rgba(124, 199, 255, 0.72) !important;
7543
8702
  border-radius: 6px !important;
7544
8703
  padding: 4px 6px !important;
@@ -7546,28 +8705,36 @@ var ReviewShell = ({
7546
8705
  background: rgba(15, 23, 42, 0.9) !important;
7547
8706
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.28) !important;
7548
8707
  font: 800 11px/1.35 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace !important;
8708
+ overflow-wrap: anywhere !important;
7549
8709
  pointer-events: none !important;
7550
- white-space: nowrap !important;
8710
+ white-space: normal !important;
7551
8711
  }
7552
8712
 
7553
8713
  [${fontOverlayAttribute}] > span {
7554
- display: flex !important;
8714
+ display: grid !important;
8715
+ grid-template-columns: auto minmax(0, 1fr) !important;
7555
8716
  justify-content: space-between !important;
7556
8717
  gap: 10px !important;
7557
8718
  }
7558
8719
 
8720
+ [${fontOverlayAttribute}] > span > span:last-child {
8721
+ min-width: 0 !important;
8722
+ text-align: right !important;
8723
+ }
8724
+
7559
8725
  [${fontOverlayAttribute}][hidden] {
7560
8726
  display: none !important;
7561
8727
  }
7562
8728
  `;
7563
- (frameDocument.head ?? frameDocument.documentElement).append(style);
8729
+ frameRoot.append(style);
7564
8730
  const fontOverlay = frameDocument.createElement("div");
7565
8731
  fontOverlay.setAttribute(fontOverlayAttribute, "true");
7566
8732
  fontOverlay.hidden = true;
7567
- (frameDocument.body ?? frameDocument.documentElement).append(fontOverlay);
8733
+ frameBody.append(fontOverlay);
7568
8734
  let hoveredElement = null;
7569
- let lastSourceElement = null;
8735
+ let lastSourceTarget = null;
7570
8736
  let isSourceSelecting = false;
8737
+ let isSourcePanelPinned = false;
7571
8738
  const getFontHints = (element) => {
7572
8739
  if (!element) return [];
7573
8740
  const values = [];
@@ -7591,10 +8758,13 @@ var ReviewShell = ({
7591
8758
  const rect = element.getBoundingClientRect();
7592
8759
  const frameWidth = frameDocument.documentElement.clientWidth;
7593
8760
  const showAbove = rect.top > 48;
7594
- const left = Math.max(4, Math.min(rect.left, frameWidth - 96));
7595
8761
  const top = Math.max(4, showAbove ? rect.top : rect.bottom);
7596
8762
  fontOverlay.replaceChildren();
7597
8763
  fontOverlay.style.minWidth = "72px";
8764
+ fontOverlay.style.left = "4px";
8765
+ fontOverlay.style.top = `${top}px`;
8766
+ fontOverlay.style.transform = showAbove ? "translateY(calc(-100% - 6px))" : "translateY(6px)";
8767
+ fontOverlay.style.visibility = "hidden";
7598
8768
  const rows = values.map(({ tag, value }) => {
7599
8769
  const row = frameDocument.createElement("span");
7600
8770
  const tagText = frameDocument.createElement("span");
@@ -7605,54 +8775,70 @@ var ReviewShell = ({
7605
8775
  return row;
7606
8776
  });
7607
8777
  fontOverlay.append(...rows);
7608
- fontOverlay.style.left = `${left}px`;
7609
- fontOverlay.style.top = `${top}px`;
7610
- fontOverlay.style.transform = showAbove ? "translateY(calc(-100% - 6px))" : "translateY(6px)";
7611
8778
  fontOverlay.hidden = false;
8779
+ const overlayWidth = fontOverlay.getBoundingClientRect().width;
8780
+ const left = Math.max(
8781
+ 4,
8782
+ Math.min(rect.left, frameWidth - overlayWidth - 4)
8783
+ );
8784
+ fontOverlay.style.left = `${left}px`;
8785
+ fontOverlay.style.visibility = "";
7612
8786
  };
7613
8787
  const setHoveredElement = (element) => {
7614
- if (hoveredElement !== element) {
7615
- hoveredElement?.removeAttribute(hoverAttribute);
7616
- hoveredElement = element;
7617
- hoveredElement?.setAttribute(hoverAttribute, "true");
7618
- }
8788
+ hoveredElement = element;
7619
8789
  updateFontOverlay(element);
7620
8790
  };
7621
8791
  const setSourceSelecting = (isSelecting) => {
7622
8792
  isSourceSelecting = isSelecting;
7623
8793
  if (isSelecting) {
8794
+ isSourcePanelPinned = false;
7624
8795
  frameDocument.documentElement.setAttribute(optionAttribute, "true");
7625
- setHoveredElement(lastSourceElement);
8796
+ const candidate = showSourceOutlineForTarget(lastSourceTarget);
8797
+ setHoveredElement(candidate?.element ?? hoveredElement);
7626
8798
  return;
7627
8799
  }
7628
8800
  setHoveredElement(null);
7629
8801
  fontOverlay.hidden = true;
7630
8802
  frameDocument.documentElement.removeAttribute(optionAttribute);
8803
+ if (!isSourcePanelPinned && !sourceInspectorInteractionRef.current) {
8804
+ clearSourceInspector();
8805
+ }
7631
8806
  };
7632
8807
  const handleMouseMove = (event) => {
7633
- lastSourceElement = getSourceHintElement(event.target);
8808
+ lastSourceTarget = event.target;
8809
+ const candidates = getSourceCandidates(event.target);
8810
+ const sourceElement = candidates[0]?.element ?? null;
7634
8811
  if (event.altKey && !isSourceSelecting) {
7635
8812
  setSourceSelecting(true);
7636
8813
  }
7637
- setHoveredElement(isSourceSelecting ? lastSourceElement : null);
8814
+ if (isSourceSelecting && !isSourcePanelPinned) {
8815
+ showSourceOutlineForTarget(event.target);
8816
+ }
8817
+ setHoveredElement(isSourceSelecting ? sourceElement : null);
7638
8818
  };
7639
8819
  const handleClick = (event) => {
7640
8820
  if (!isSourceSelecting && !event.altKey) return;
7641
8821
  event.preventDefault();
7642
8822
  event.stopPropagation();
7643
8823
  event.stopImmediatePropagation();
7644
- const source2 = getElementSourceHint(event.target);
7645
- if (!source2?.file) {
8824
+ const candidates = showSourceInspectorForTarget(event.target, true);
8825
+ if (!candidates.length) {
7646
8826
  showToast("Source hint not found");
8827
+ isSourcePanelPinned = false;
7647
8828
  setSourceSelecting(false);
7648
8829
  return;
7649
8830
  }
7650
- const didOpen = openSourceInEditor(source2, sourceRoot);
7651
- showToast(didOpen ? "Source opened" : "Source root required");
8831
+ isSourcePanelPinned = true;
7652
8832
  setSourceSelecting(false);
7653
8833
  };
7654
8834
  const isOptionKeyEvent = (event) => event.key === "Alt" || event.code === "AltLeft" || event.code === "AltRight" || event.altKey;
7655
8835
  const handleKeyDown = (event) => {
8836
+ if (event.key === "Escape") {
8837
+ isSourcePanelPinned = false;
8838
+ setSourceSelecting(false);
8839
+ clearSourceInspector();
8840
+ return;
8841
+ }
7656
8842
  if (!isOptionKeyEvent(event)) return;
7657
8843
  cancelReviewMode();
7658
8844
  setSourceSelecting(true);
@@ -7661,43 +8847,60 @@ var ReviewShell = ({
7661
8847
  if (isOptionKeyEvent(event) || !event.altKey) setSourceSelecting(false);
7662
8848
  };
7663
8849
  const handleBlur = () => {
8850
+ isSourcePanelPinned = false;
8851
+ setSourceSelecting(false);
8852
+ };
8853
+ const handleWindowPointerDown = (event) => {
8854
+ const target2 = event.target;
8855
+ if (target2 instanceof Element && target2.closest(".df-review-source-popover")) {
8856
+ sourceInspectorInteractionRef.current = true;
8857
+ return;
8858
+ }
8859
+ isSourcePanelPinned = false;
8860
+ sourceInspectorInteractionRef.current = false;
7664
8861
  setSourceSelecting(false);
8862
+ clearSourceInspector();
7665
8863
  };
7666
8864
  frameDocument.addEventListener("mousemove", handleMouseMove, true);
7667
8865
  frameDocument.addEventListener("click", handleClick, true);
7668
8866
  frameDocument.addEventListener("keydown", handleKeyDown, true);
7669
8867
  frameDocument.addEventListener("keyup", handleKeyUp, true);
7670
- frameDocument.defaultView?.addEventListener("blur", handleBlur);
7671
8868
  window.addEventListener("keydown", handleKeyDown, true);
7672
8869
  window.addEventListener("keyup", handleKeyUp, true);
7673
8870
  window.addEventListener("blur", handleBlur);
8871
+ window.addEventListener("pointerdown", handleWindowPointerDown, true);
7674
8872
  sourceShortcutCleanupRef.current = () => {
7675
8873
  frameDocument.removeEventListener("mousemove", handleMouseMove, true);
7676
8874
  frameDocument.removeEventListener("click", handleClick, true);
7677
8875
  frameDocument.removeEventListener("keydown", handleKeyDown, true);
7678
8876
  frameDocument.removeEventListener("keyup", handleKeyUp, true);
7679
- frameDocument.defaultView?.removeEventListener("blur", handleBlur);
7680
8877
  window.removeEventListener("keydown", handleKeyDown, true);
7681
8878
  window.removeEventListener("keyup", handleKeyUp, true);
7682
8879
  window.removeEventListener("blur", handleBlur);
8880
+ window.removeEventListener("pointerdown", handleWindowPointerDown, true);
8881
+ isSourcePanelPinned = false;
7683
8882
  setSourceSelecting(false);
7684
8883
  style.remove();
7685
8884
  fontOverlay.remove();
7686
8885
  };
7687
8886
  }, [
7688
8887
  cancelReviewMode,
8888
+ clearSourceInspector,
7689
8889
  cleanupSourceOpenShortcut,
7690
8890
  iframeRef,
8891
+ isSourceInspectorEnabled,
7691
8892
  showToast,
7692
- sourceRoot
8893
+ showSourceOutlineForTarget,
8894
+ showSourceInspectorForTarget
7693
8895
  ]);
7694
8896
  useEffect10(() => {
7695
8897
  return cleanupSourceOpenShortcut;
7696
8898
  }, [cleanupSourceOpenShortcut]);
7697
8899
  const loadTargetFrame = useCallback11(() => {
7698
8900
  initReviewKit();
8901
+ refreshTargetFigmaConfig();
7699
8902
  bindSourceOpenShortcut();
7700
- }, [bindSourceOpenShortcut, initReviewKit]);
8903
+ }, [bindSourceOpenShortcut, initReviewKit, refreshTargetFigmaConfig]);
7701
8904
  useEffect10(() => {
7702
8905
  const frame = window.requestAnimationFrame(bindSourceOpenShortcut);
7703
8906
  return () => window.cancelAnimationFrame(frame);
@@ -7752,6 +8955,19 @@ var ReviewShell = ({
7752
8955
  `qa:${numberedItem.item.id}`,
7753
8956
  "QA prompt copied"
7754
8957
  );
8958
+ const copyItemLink = (numberedItem) => {
8959
+ const { item } = numberedItem;
8960
+ return copyPrompt(
8961
+ getShellUrlForItem(
8962
+ getItemTarget(item, reviewPathPrefix),
8963
+ getRestoredSize(item, viewportPresets),
8964
+ item.id,
8965
+ source
8966
+ ).href,
8967
+ `link:${item.id}`,
8968
+ "QA link copied"
8969
+ );
8970
+ };
7755
8971
  const removeItem = (item) => removeReviewItem({
7756
8972
  activeAdapterEntry,
7757
8973
  isRemoteSource,
@@ -7794,15 +9010,25 @@ var ReviewShell = ({
7794
9010
  onOpenSettings: openFigmaSettings
7795
9011
  }
7796
9012
  ),
9013
+ currentPagePresenceUsers.length > 0 && /* @__PURE__ */ jsx17("div", { className: "df-review-presence-row", children: /* @__PURE__ */ jsx17(
9014
+ PresenceOverlay,
9015
+ {
9016
+ presenceSessionId,
9017
+ users: currentPagePresenceUsers
9018
+ }
9019
+ ) }),
7797
9020
  isSitemapOpen && /* @__PURE__ */ jsx17(
7798
9021
  SitemapModal,
7799
9022
  {
7800
9023
  pages,
7801
9024
  activeRoute,
9025
+ allQaCount,
9026
+ isAllQaVisible,
7802
9027
  pageQaCounts,
7803
9028
  pagePresenceUsers,
7804
9029
  getPageTarget: (href) => normalizeTarget(href, reviewPathPrefix),
7805
9030
  onClose: () => setIsSitemapOpen(false),
9031
+ onSelectAllQa: selectAllQa,
7806
9032
  onSelectPage: selectPage
7807
9033
  }
7808
9034
  ),
@@ -7861,29 +9087,32 @@ var ReviewShell = ({
7861
9087
  {
7862
9088
  activeAdapterEntry,
7863
9089
  activeItems,
7864
- currentPagePresenceUsers,
9090
+ activeRemainingItemCount,
7865
9091
  currentPresetScope,
7866
9092
  filteredNumberedActiveItems,
7867
9093
  getItemPresetScope,
7868
9094
  hiddenOverlayItemIds,
7869
9095
  isListVisible,
9096
+ isAllQaVisible,
7870
9097
  isRemoteSource,
7871
- presenceSessionId,
7872
9098
  copiedPromptKey,
7873
9099
  qaFilter,
7874
9100
  qaFilterCounts,
9101
+ qaStatusFilter,
9102
+ qaStatusFilterCounts,
7875
9103
  remoteAdapterEntry,
7876
9104
  selectedItemId,
7877
9105
  showSourceSelect,
7878
- sourceRoot,
7879
9106
  source,
7880
9107
  sourceEntries,
7881
9108
  onChangeItemStatus: changeItemStatus,
7882
9109
  onClearSelectedItem: clearSelectedReviewItem,
7883
9110
  onChangeReviewSource: changeReviewSource,
9111
+ onCopyItemLink: (numberedItem) => void copyItemLink(numberedItem),
7884
9112
  onCopyItemPrompt: (numberedItem) => void copyItemPrompt(numberedItem),
7885
9113
  onEditItem: setEditingItem,
7886
9114
  onQaFilterChange: setQaFilter,
9115
+ onQaStatusFilterChange: setQaStatusFilter,
7887
9116
  onRefreshReviewData: refreshReviewData2,
7888
9117
  onRemoveItem: removeItem,
7889
9118
  onRestoreReviewItem: restoreReviewItem,
@@ -7896,6 +9125,7 @@ var ReviewShell = ({
7896
9125
  {
7897
9126
  canWriteArea,
7898
9127
  canWriteDom,
9128
+ figmaFrameUrl,
7899
9129
  frameScrollRef,
7900
9130
  iframeRef,
7901
9131
  isRulerAvailable,
@@ -7914,7 +9144,72 @@ var ReviewShell = ({
7914
9144
  onLoadTarget: loadTargetFrame,
7915
9145
  onSetReviewMode: setReviewMode
7916
9146
  }
7917
- )
9147
+ ),
9148
+ sourceInspectorState && /* @__PURE__ */ jsxs15(Fragment4, { children: [
9149
+ /* @__PURE__ */ jsx17(
9150
+ "div",
9151
+ {
9152
+ className: `df-review-source-outline${sourceInspectorState.isPinned ? " is-pinned" : ""}`,
9153
+ style: {
9154
+ height: `${sourceInspectorState.rect.height}px`,
9155
+ left: `${sourceInspectorState.rect.left}px`,
9156
+ top: `${sourceInspectorState.rect.top}px`,
9157
+ width: `${sourceInspectorState.rect.width}px`
9158
+ }
9159
+ }
9160
+ ),
9161
+ sourceInspectorState.candidates.length > 0 && /* @__PURE__ */ jsxs15(
9162
+ "div",
9163
+ {
9164
+ className: `df-review-source-popover${sourceInspectorState.isPinned ? " is-pinned" : ""}`,
9165
+ style: {
9166
+ left: sourceInspectorState.panelRight === null ? `${sourceInspectorState.panelLeft}px` : void 0,
9167
+ maxWidth: `${sourceInspectorState.panelMaxWidth}px`,
9168
+ right: sourceInspectorState.panelRight === null ? void 0 : `${sourceInspectorState.panelRight}px`,
9169
+ top: `${sourceInspectorState.panelTop}px`
9170
+ },
9171
+ onPointerDown: () => {
9172
+ sourceInspectorInteractionRef.current = true;
9173
+ },
9174
+ onPointerEnter: () => {
9175
+ sourceInspectorInteractionRef.current = true;
9176
+ },
9177
+ onPointerLeave: () => {
9178
+ sourceInspectorInteractionRef.current = false;
9179
+ },
9180
+ onClick: (event) => event.stopPropagation(),
9181
+ children: [
9182
+ /* @__PURE__ */ jsx17("div", { className: "df-review-source-popover-close", children: /* @__PURE__ */ jsx17(
9183
+ "button",
9184
+ {
9185
+ "aria-label": "Close source candidates",
9186
+ type: "button",
9187
+ onClick: clearSourceInspector,
9188
+ children: "\xD7"
9189
+ }
9190
+ ) }),
9191
+ /* @__PURE__ */ jsx17("div", { className: "df-review-source-candidate-list", children: sourceInspectorState.candidates.map((candidate) => /* @__PURE__ */ jsx17(
9192
+ "button",
9193
+ {
9194
+ className: "df-review-source-candidate",
9195
+ type: "button",
9196
+ onClick: (event) => {
9197
+ event.preventDefault();
9198
+ event.stopPropagation();
9199
+ openSourceCandidate(candidate);
9200
+ },
9201
+ children: /* @__PURE__ */ jsxs15("span", { className: "df-review-source-candidate-main", children: [
9202
+ /* @__PURE__ */ jsx17("strong", { children: candidate.label }),
9203
+ /* @__PURE__ */ jsx17("span", { children: candidate.filePath }),
9204
+ /* @__PURE__ */ jsx17("small", { children: candidate.positionLabel || (candidate.usesPosition ? "" : "file only") })
9205
+ ] })
9206
+ },
9207
+ candidate.id
9208
+ )) })
9209
+ ]
9210
+ }
9211
+ )
9212
+ ] })
7918
9213
  ]
7919
9214
  }
7920
9215
  );
@@ -8128,15 +9423,16 @@ lucide-react/dist/esm/shared/src/utils/hasA11yProp.mjs:
8128
9423
  lucide-react/dist/esm/context.mjs:
8129
9424
  lucide-react/dist/esm/Icon.mjs:
8130
9425
  lucide-react/dist/esm/createLucideIcon.mjs:
9426
+ lucide-react/dist/esm/icons/bot.mjs:
8131
9427
  lucide-react/dist/esm/icons/circle-question-mark.mjs:
8132
9428
  lucide-react/dist/esm/icons/copy.mjs:
8133
9429
  lucide-react/dist/esm/icons/external-link.mjs:
8134
9430
  lucide-react/dist/esm/icons/eye-off.mjs:
8135
9431
  lucide-react/dist/esm/icons/eye.mjs:
8136
- lucide-react/dist/esm/icons/file-code-corner.mjs:
8137
9432
  lucide-react/dist/esm/icons/grip-vertical.mjs:
8138
9433
  lucide-react/dist/esm/icons/image.mjs:
8139
9434
  lucide-react/dist/esm/icons/layout-grid.mjs:
9435
+ lucide-react/dist/esm/icons/link-2.mjs:
8140
9436
  lucide-react/dist/esm/icons/list-filter.mjs:
8141
9437
  lucide-react/dist/esm/icons/map.mjs:
8142
9438
  lucide-react/dist/esm/icons/maximize-2.mjs:
@@ -8151,7 +9447,6 @@ lucide-react/dist/esm/icons/smartphone.mjs:
8151
9447
  lucide-react/dist/esm/icons/square-mouse-pointer.mjs:
8152
9448
  lucide-react/dist/esm/icons/sticky-note.mjs:
8153
9449
  lucide-react/dist/esm/icons/upload.mjs:
8154
- lucide-react/dist/esm/icons/users.mjs:
8155
9450
  lucide-react/dist/esm/icons/x.mjs:
8156
9451
  lucide-react/dist/esm/lucide-react.mjs:
8157
9452
  (**