@designfever/web-review-kit 0.3.0 → 0.4.1

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-6L2KJ7XL.js";
7
7
 
8
8
  // src/react-shell.tsx
9
9
  import React2 from "react";
@@ -148,9 +148,11 @@ 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);
155
+ --df-review-surface: var(--df-review-color-surface);
154
156
  --df-review-topbar: var(--df-review-color-surface);
155
157
  --df-review-panel: var(--df-review-color-panel);
156
158
  --df-review-panel-strong: var(--df-review-color-panel-strong);
@@ -243,6 +245,7 @@ function ensureReviewShellStyle() {
243
245
  --df-review-shadow-device: 0 24px 60px rgba(15, 23, 42, 0.18);
244
246
  --df-review-shadow-panel: 0 18px 48px rgba(15, 23, 42, 0.18);
245
247
  --df-review-shadow-modal: 0 24px 70px rgba(15, 23, 42, 0.2);
248
+ --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
249
  }
247
250
 
248
251
  button,
@@ -260,7 +263,7 @@ function ensureReviewShellStyle() {
260
263
  --df-review-frame-gutter-x: var(--df-review-space-4);
261
264
  display: grid;
262
265
  grid-template-columns: minmax(0, 1fr) 0 32px;
263
- grid-template-rows: auto minmax(0, 1fr);
266
+ grid-template-rows: auto auto minmax(0, 1fr);
264
267
  width: 100%;
265
268
  height: 100%;
266
269
  overflow: hidden;
@@ -287,6 +290,20 @@ function ensureReviewShellStyle() {
287
290
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.025);
288
291
  }
289
292
 
293
+ .df-review-presence-row {
294
+ grid-column: 1;
295
+ grid-row: 2;
296
+ position: relative;
297
+ z-index: 590;
298
+ display: flex;
299
+ align-items: center;
300
+ justify-content: flex-start;
301
+ min-width: 0;
302
+ padding: var(--df-review-space-2) var(--df-review-frame-gutter-x);
303
+ border-bottom: 1px solid var(--df-review-line-soft);
304
+ background: var(--df-review-panel);
305
+ }
306
+
290
307
  .df-review-address {
291
308
  display: grid;
292
309
  grid-template-columns: auto minmax(160px, 1fr) auto auto;
@@ -347,7 +364,7 @@ function ensureReviewShellStyle() {
347
364
  .df-review-prompt-block-header button:hover,
348
365
  .df-review-item-actions button:hover,
349
366
  .df-review-item-visibility:hover,
350
- .df-review-item-source-open:hover,
367
+ .df-review-item-link-copy:hover,
351
368
  .df-review-item-prompt-copy:hover,
352
369
  .df-review-item-delete:hover,
353
370
  .df-review-presets button.is-active,
@@ -410,7 +427,7 @@ function ensureReviewShellStyle() {
410
427
  z-index: 1;
411
428
  display: grid;
412
429
  grid-template-rows: auto minmax(0, 1fr);
413
- width: min(760px, calc(100vw - 48px));
430
+ width: min(940px, calc(100vw - 48px));
414
431
  max-height: min(720px, calc(100vh - 48px));
415
432
  overflow: hidden;
416
433
  border: 1px solid var(--df-review-line);
@@ -465,18 +482,45 @@ function ensureReviewShellStyle() {
465
482
  background: var(--df-review-control-hover);
466
483
  }
467
484
 
485
+ .df-review-sitemap-controls {
486
+ display: flex;
487
+ align-items: center;
488
+ gap: 8px;
489
+ min-width: 0;
490
+ padding: 10px 12px;
491
+ border-bottom: 1px solid var(--df-review-line-soft);
492
+ background: var(--df-review-panel);
493
+ }
494
+
495
+ .df-review-sitemap-controls select {
496
+ min-width: 138px;
497
+ min-height: 32px;
498
+ border: 1px solid var(--df-review-line-soft);
499
+ border-radius: var(--df-review-radius-sm);
500
+ padding: 0 26px 0 10px;
501
+ color: var(--df-review-text);
502
+ background: var(--df-review-control);
503
+ box-shadow: var(--df-review-shadow-control);
504
+ font-size: var(--df-review-font-size-xs);
505
+ font-weight: 850;
506
+ }
507
+
468
508
  .df-review-sitemap-list {
509
+ --df-review-sitemap-grid-template: minmax(190px, 1fr) 74px 78px 64px minmax(108px, 160px);
510
+ position: relative;
469
511
  display: grid;
470
512
  align-content: start;
471
513
  min-height: 0;
472
- overflow: auto;
514
+ overflow-x: hidden;
515
+ overflow-y: auto;
516
+ overscroll-behavior: contain;
473
517
  padding: 8px;
474
518
  }
475
519
 
476
520
  .df-review-sitemap-table-head,
477
521
  .df-review-sitemap-row {
478
522
  display: grid;
479
- grid-template-columns: minmax(160px, 1fr) 70px 78px minmax(96px, 140px);
523
+ grid-template-columns: var(--df-review-sitemap-grid-template);
480
524
  align-items: center;
481
525
  column-gap: 0;
482
526
  }
@@ -484,20 +528,64 @@ function ensureReviewShellStyle() {
484
528
  .df-review-sitemap-table-head {
485
529
  position: sticky;
486
530
  top: 0;
487
- z-index: 1;
531
+ z-index: 3;
488
532
  min-height: 32px;
489
533
  border-bottom: 1px solid var(--df-review-line);
490
534
  padding: 0 10px;
491
- background: var(--df-review-surface);
535
+ background: var(--df-review-panel);
536
+ box-shadow:
537
+ 0 -8px 0 0 var(--df-review-panel),
538
+ 0 1px 0 var(--df-review-line);
492
539
  color: var(--df-review-muted);
493
540
  font-size: var(--df-review-font-size-xs);
494
- font-weight: 900;
495
- letter-spacing: 0.04em;
541
+ font-weight: 720;
542
+ letter-spacing: 0.03em;
496
543
  text-transform: uppercase;
497
544
  }
498
545
 
499
- .df-review-sitemap-table-head span:not(:first-child) {
546
+ .df-review-sitemap-sort {
547
+ display: inline-flex;
548
+ align-items: center;
549
+ justify-content: flex-end;
550
+ gap: 4px;
551
+ min-width: 0;
552
+ min-height: 28px;
553
+ border: 0;
554
+ border-radius: 0;
555
+ padding: 0 0 0 8px;
556
+ color: inherit;
557
+ background: transparent;
558
+ box-shadow: none;
559
+ font: inherit;
560
+ letter-spacing: inherit;
500
561
  text-align: right;
562
+ text-transform: inherit;
563
+ cursor: pointer;
564
+ }
565
+
566
+ .df-review-sitemap-sort.is-page {
567
+ justify-content: flex-start;
568
+ padding-left: 0;
569
+ text-align: left;
570
+ }
571
+
572
+ .df-review-sitemap-sort-indicator {
573
+ width: 8px;
574
+ min-width: 8px;
575
+ color: var(--df-review-accent);
576
+ text-align: center;
577
+ }
578
+
579
+ .df-review-sitemap-sort-label {
580
+ min-width: 0;
581
+ overflow: hidden;
582
+ text-overflow: ellipsis;
583
+ white-space: nowrap;
584
+ }
585
+
586
+ .df-review-sitemap-sort:hover,
587
+ .df-review-sitemap-sort.is-active {
588
+ color: var(--df-review-text);
501
589
  }
502
590
 
503
591
  .df-review-sitemap-row {
@@ -515,6 +603,18 @@ function ensureReviewShellStyle() {
515
603
  cursor: pointer;
516
604
  }
517
605
 
606
+ .df-review-sitemap-row.is-summary {
607
+ position: sticky;
608
+ bottom: 0;
609
+ z-index: 3;
610
+ border-top: 1px solid var(--df-review-line);
611
+ border-bottom: 0;
612
+ background: var(--df-review-panel);
613
+ box-shadow:
614
+ 0 8px 0 0 var(--df-review-panel),
615
+ 0 -1px 0 var(--df-review-line);
616
+ }
617
+
518
618
  .df-review-sitemap-row.is-folder {
519
619
  cursor: default;
520
620
  }
@@ -531,11 +631,12 @@ function ensureReviewShellStyle() {
531
631
  .df-review-sitemap-path {
532
632
  display: inline-flex;
533
633
  align-items: center;
634
+ gap: 7px;
534
635
  min-width: 0;
535
636
  overflow-wrap: anywhere;
536
637
  color: var(--df-review-text);
537
638
  font-size: var(--df-review-font-size-md);
538
- font-weight: 800;
639
+ font-weight: 650;
539
640
  line-height: 1.35;
540
641
  }
541
642
 
@@ -547,26 +648,33 @@ function ensureReviewShellStyle() {
547
648
  flex: 0 0 auto;
548
649
  color: var(--df-review-muted);
549
650
  font-family: var(--df-review-font-mono);
550
- font-weight: 700;
651
+ font-weight: 500;
551
652
  white-space: pre;
552
653
  }
553
654
 
655
+ .df-review-sitemap-label {
656
+ min-width: 0;
657
+ overflow: hidden;
658
+ text-overflow: ellipsis;
659
+ }
660
+
554
661
  .df-review-sitemap-cell {
555
662
  min-width: 0;
556
663
  color: var(--df-review-muted);
557
664
  font-size: var(--df-review-font-size-sm);
558
665
  font-variant-numeric: tabular-nums;
559
- font-weight: 900;
666
+ font-weight: 650;
560
667
  line-height: 1;
561
668
  text-align: right;
562
669
  }
563
670
 
564
- .df-review-sitemap-cell.is-local {
671
+ .df-review-sitemap-cell.is-total {
565
672
  color: var(--df-review-accent);
673
+ font-weight: 760;
566
674
  }
567
675
 
568
- .df-review-sitemap-cell.is-remote {
569
- color: var(--df-review-area);
676
+ .df-review-sitemap-cell.is-total strong {
677
+ font: inherit;
570
678
  }
571
679
 
572
680
  .df-review-sitemap-cell.is-online {
@@ -1379,7 +1487,7 @@ function ensureReviewShellStyle() {
1379
1487
 
1380
1488
  .df-review-side-rail {
1381
1489
  grid-column: 3;
1382
- grid-row: 1 / span 2;
1490
+ grid-row: 1 / span 3;
1383
1491
  position: relative;
1384
1492
  z-index: 600;
1385
1493
  display: flex;
@@ -1439,7 +1547,7 @@ function ensureReviewShellStyle() {
1439
1547
 
1440
1548
  .df-review-qa-panel {
1441
1549
  grid-column: 2;
1442
- grid-row: 1 / span 2;
1550
+ grid-row: 1 / span 3;
1443
1551
  position: relative;
1444
1552
  z-index: 600;
1445
1553
  display: grid;
@@ -1512,101 +1620,114 @@ function ensureReviewShellStyle() {
1512
1620
  font-variant-numeric: tabular-nums;
1513
1621
  }
1514
1622
 
1515
- .df-review-presence-row {
1516
- display: grid;
1517
- grid-template-columns: minmax(0, 1fr);
1518
- align-items: start;
1623
+ .df-review-presence-overlay {
1624
+ display: flex;
1625
+ align-items: center;
1626
+ flex-wrap: wrap;
1519
1627
  gap: 5px;
1520
- min-width: 0;
1628
+ max-width: 100%;
1629
+ pointer-events: none;
1521
1630
  }
1522
1631
 
1523
- .df-review-presence-label {
1632
+ .df-review-presence-row .df-review-presence-overlay {
1633
+ width: 100%;
1634
+ max-width: 1440px;
1635
+ margin: 0 auto;
1636
+ }
1637
+
1638
+ .df-review-presence-chip {
1639
+ --df-review-presence-color: var(--df-review-accent);
1524
1640
  display: inline-flex;
1525
1641
  align-items: center;
1526
- gap: 4px;
1527
1642
  min-width: 0;
1528
- color: var(--df-review-muted);
1643
+ max-width: min(220px, calc(100% - 20px));
1644
+ min-height: 24px;
1645
+ border: 1px solid rgba(15, 23, 42, 0.12);
1646
+ border-radius: var(--df-review-radius-pill);
1647
+ padding: 0 8px;
1648
+ color: #17202c;
1649
+ background: rgba(255, 255, 255, 0.92);
1650
+ box-shadow: 0 8px 22px rgba(15, 23, 42, 0.14);
1529
1651
  font-size: var(--df-review-font-size-2xs);
1530
1652
  font-weight: 900;
1531
- line-height: 1;
1653
+ line-height: 1.1;
1654
+ overflow: hidden;
1655
+ text-overflow: ellipsis;
1532
1656
  white-space: nowrap;
1657
+ backdrop-filter: blur(8px);
1533
1658
  }
1534
1659
 
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;
1660
+ .df-review-presence-row .df-review-presence-chip {
1661
+ max-width: none;
1543
1662
  }
1544
1663
 
1545
- .df-review-presence-list {
1546
- display: flex;
1547
- align-items: center;
1548
- gap: 4px;
1549
- min-width: 0;
1550
- flex-wrap: wrap;
1664
+ .df-review-presence-chip.is-self {
1665
+ border-color: var(--df-review-presence-color);
1666
+ box-shadow:
1667
+ inset 0 0 0 1px var(--df-review-presence-color),
1668
+ 0 8px 22px rgba(15, 23, 42, 0.14);
1551
1669
  }
1552
1670
 
1553
- .df-review-presence-chip {
1554
- --df-review-presence-color: var(--df-review-accent);
1671
+ .df-review-presence-more {
1555
1672
  display: inline-flex;
1556
1673
  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);
1674
+ justify-content: center;
1675
+ min-width: 28px;
1676
+ min-height: 24px;
1677
+ border: 1px solid rgba(15, 23, 42, 0.12);
1562
1678
  border-radius: var(--df-review-radius-pill);
1563
- padding: 0 7px;
1564
- color: var(--df-review-text);
1565
- background: var(--df-review-chip-bg);
1679
+ padding: 0 8px;
1680
+ color: #17202c;
1681
+ background: rgba(255, 255, 255, 0.92);
1682
+ box-shadow: 0 8px 22px rgba(15, 23, 42, 0.14);
1683
+ cursor: pointer;
1684
+ font: inherit;
1566
1685
  font-size: var(--df-review-font-size-2xs);
1567
1686
  font-weight: 900;
1568
- line-height: 1.1;
1569
- white-space: nowrap;
1687
+ line-height: 1;
1688
+ pointer-events: auto;
1689
+ backdrop-filter: blur(8px);
1570
1690
  }
1571
1691
 
1572
- .df-review-presence-chip.is-self {
1573
- border-color: var(--df-review-presence-color);
1574
- background: var(--df-review-accent-soft);
1692
+ .df-review-presence-more:hover {
1693
+ border-color: rgba(0, 102, 255, 0.42);
1694
+ color: #005be8;
1695
+ background: rgba(255, 255, 255, 0.98);
1575
1696
  }
1576
1697
 
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);
1698
+ .df-review-list-controls {
1699
+ display: flex;
1700
+ align-items: center;
1701
+ gap: 4px;
1583
1702
  }
1584
1703
 
1585
- .df-review-presence-name {
1586
- min-width: 0;
1704
+ .df-review-source-select,
1705
+ .df-review-status-filter-select {
1706
+ min-height: 30px;
1707
+ border: 1px solid var(--df-review-line-soft);
1708
+ border-radius: var(--df-review-radius-sm);
1709
+ padding: 0 24px 0 8px;
1710
+ color: var(--df-review-text);
1711
+ background: var(--df-review-control);
1712
+ box-shadow: var(--df-review-shadow-control);
1713
+ font-size: var(--df-review-font-size-xs);
1714
+ font-weight: 800;
1587
1715
  }
1588
1716
 
1589
- .df-review-list-controls {
1590
- display: flex;
1591
- align-items: center;
1592
- gap: 4px;
1593
- }
1717
+ .df-review-source-select {
1718
+ width: 104px;
1719
+ }
1594
1720
 
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
- }
1721
+ .df-review-status-filter-select {
1722
+ width: 124px;
1723
+ }
1607
1724
 
1608
- .df-review-source-refresh {
1609
- position: relative;
1725
+ .df-review-list-title .df-review-status-filter-select {
1726
+ margin-left: auto;
1727
+ }
1728
+
1729
+ .df-review-source-refresh {
1730
+ position: relative;
1610
1731
  display: inline-grid;
1611
1732
  place-items: center;
1612
1733
  width: 30px;
@@ -1942,6 +2063,34 @@ function ensureReviewShellStyle() {
1942
2063
  background: var(--df-review-area-soft);
1943
2064
  }
1944
2065
 
2066
+ .df-review-source-select,
2067
+ .df-review-status-filter-select,
2068
+ .df-review-item-status-select,
2069
+ .df-review-item-status-select.is-status-todo,
2070
+ .df-review-item-status-select.is-status-doing,
2071
+ .df-review-item-status-select.is-status-review,
2072
+ .df-review-item-status-select.is-status-hold,
2073
+ .df-review-item-status-select.is-status-done,
2074
+ .df-review-sitemap-controls select {
2075
+ --df-review-select-padding-x: 12px;
2076
+ --df-review-select-chevron-size: 14px;
2077
+ --df-review-select-chevron-gap: 8px;
2078
+ appearance: none;
2079
+ -webkit-appearance: none;
2080
+ background-image: var(--df-review-select-chevron);
2081
+ background-repeat: no-repeat;
2082
+ background-position: right var(--df-review-select-padding-x) center;
2083
+ background-size:
2084
+ var(--df-review-select-chevron-size)
2085
+ var(--df-review-select-chevron-size);
2086
+ padding-right: calc(
2087
+ var(--df-review-select-padding-x) +
2088
+ var(--df-review-select-chevron-size) +
2089
+ var(--df-review-select-chevron-gap)
2090
+ );
2091
+ padding-left: var(--df-review-select-padding-x);
2092
+ }
2093
+
1945
2094
  .df-review-item-status-badge.is-error {
1946
2095
  border-color: rgba(255, 143, 97, 0.36);
1947
2096
  color: var(--df-review-danger);
@@ -1967,8 +2116,8 @@ function ensureReviewShellStyle() {
1967
2116
 
1968
2117
  .df-review-item-delete,
1969
2118
  .df-review-item-edit,
2119
+ .df-review-item-link-copy,
1970
2120
  .df-review-item-prompt-copy,
1971
- .df-review-item-source-open,
1972
2121
  .df-review-item-visibility {
1973
2122
  display: inline-grid;
1974
2123
  place-items: center;
@@ -1985,7 +2134,7 @@ function ensureReviewShellStyle() {
1985
2134
 
1986
2135
  .df-review-item-visibility:hover,
1987
2136
  .df-review-item-edit:hover,
1988
- .df-review-item-source-open:hover,
2137
+ .df-review-item-link-copy:hover,
1989
2138
  .df-review-item-prompt-copy:hover {
1990
2139
  border-color: rgba(124, 199, 255, 0.34);
1991
2140
  color: var(--df-review-accent);
@@ -2007,14 +2156,15 @@ function ensureReviewShellStyle() {
2007
2156
  color: var(--df-review-subtle);
2008
2157
  }
2009
2158
 
2159
+ .df-review-item-link-copy.is-copied,
2010
2160
  .df-review-item-prompt-copy.is-copied {
2011
2161
  color: var(--df-review-accent);
2012
2162
  }
2013
2163
 
2014
2164
  .df-review-item-delete svg,
2015
2165
  .df-review-item-edit svg,
2166
+ .df-review-item-link-copy svg,
2016
2167
  .df-review-item-prompt-copy svg,
2017
- .df-review-item-source-open svg,
2018
2168
  .df-review-item-visibility svg {
2019
2169
  width: 14px;
2020
2170
  height: 14px;
@@ -2037,6 +2187,15 @@ function ensureReviewShellStyle() {
2037
2187
  cursor: auto;
2038
2188
  }
2039
2189
 
2190
+ .df-review-item-prompt-actions {
2191
+ display: inline-flex;
2192
+ grid-column: 2;
2193
+ align-items: center;
2194
+ justify-self: end;
2195
+ min-width: 0;
2196
+ cursor: auto;
2197
+ }
2198
+
2040
2199
  .df-review-item-remote-actions {
2041
2200
  display: inline-flex;
2042
2201
  grid-column: 3;
@@ -2114,7 +2273,7 @@ function ensureReviewShellStyle() {
2114
2273
 
2115
2274
  .df-review-stage {
2116
2275
  grid-column: 1;
2117
- grid-row: 2;
2276
+ grid-row: 3;
2118
2277
  display: grid;
2119
2278
  min-width: 0;
2120
2279
  min-height: 0;
@@ -2153,9 +2312,17 @@ function ensureReviewShellStyle() {
2153
2312
  height: max-content;
2154
2313
  min-width: 100%;
2155
2314
  min-height: 100%;
2156
- padding: 34px 40px 12px;
2315
+ padding: 34px 58px 12px 40px;
2157
2316
  }
2158
2317
 
2318
+ .df-review-target-stack {
2319
+ display: grid;
2320
+ justify-items: start;
2321
+ gap: 8px;
2322
+ width: max-content;
2323
+ max-width: 100%;
2324
+ }
2325
+
2159
2326
  .df-review-device {
2160
2327
  box-sizing: border-box;
2161
2328
  flex: 0 0 auto;
@@ -2178,86 +2345,264 @@ function ensureReviewShellStyle() {
2178
2345
  background: #fff;
2179
2346
  }
2180
2347
 
2181
- .df-review-device-frame {
2182
- position: relative;
2183
- box-sizing: border-box;
2184
- flex: 0 0 auto;
2348
+ .df-review-frame-link-stack {
2349
+ position: absolute;
2350
+ z-index: 14;
2351
+ top: 0;
2352
+ right: -44px;
2353
+ display: grid;
2354
+ gap: 8px;
2185
2355
  }
2186
2356
 
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);
2357
+ .df-review-frame-link {
2358
+ display: grid;
2359
+ place-items: center;
2360
+ width: 34px;
2361
+ height: 34px;
2362
+ border: 1px solid rgba(15, 23, 42, 0.16);
2363
+ border-radius: var(--df-review-radius-md);
2364
+ color: #17202c;
2365
+ background: rgba(255, 255, 255, 0.92);
2366
+ box-shadow: 0 10px 26px rgba(15, 23, 42, 0.18);
2367
+ text-decoration: none;
2368
+ backdrop-filter: blur(8px);
2369
+ transition: transform 140ms ease, border-color 140ms ease, color 140ms ease,
2370
+ background 140ms ease;
2197
2371
  }
2198
2372
 
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;
2373
+ .df-review-frame-link:hover {
2374
+ transform: translateY(-1px);
2375
+ border-color: rgba(0, 102, 255, 0.42);
2376
+ color: #005be8;
2377
+ background: rgba(255, 255, 255, 0.98);
2205
2378
  }
2206
2379
 
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;
2380
+ .df-review-frame-link svg {
2381
+ width: 18px;
2382
+ height: 18px;
2229
2383
  }
2230
2384
 
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;
2385
+ .df-review-frame-link.is-target svg {
2386
+ fill: none;
2387
+ stroke: currentColor;
2388
+ stroke-width: 2;
2253
2389
  }
2254
2390
 
2255
- .df-review-ruler-frame-label {
2256
- position: absolute;
2257
- right: 6px;
2258
- top: 50%;
2259
- transform: translateY(-50%);
2260
- display: flex;
2391
+ .df-review-frame-link.is-figma svg {
2392
+ fill: currentColor;
2393
+ stroke: none;
2394
+ }
2395
+
2396
+ .df-review-source-outline {
2397
+ position: fixed;
2398
+ z-index: 880;
2399
+ pointer-events: none;
2400
+ border: 2px solid rgba(124, 199, 255, 0.96);
2401
+ border-radius: 4px;
2402
+ box-shadow:
2403
+ 0 0 0 1px rgba(15, 18, 24, 0.58),
2404
+ 0 0 0 5px rgba(124, 199, 255, 0.16);
2405
+ }
2406
+
2407
+ .df-review-source-outline.is-pinned {
2408
+ border-color: var(--df-review-note);
2409
+ box-shadow:
2410
+ 0 0 0 1px rgba(15, 18, 24, 0.58),
2411
+ 0 0 0 5px rgba(243, 183, 95, 0.16);
2412
+ }
2413
+
2414
+ .df-review-source-popover {
2415
+ --df-review-source-popover-line: rgba(226, 233, 245, 0.16);
2416
+ --df-review-source-popover-text: #edf3fb;
2417
+ --df-review-source-popover-muted: rgba(237, 243, 251, 0.68);
2418
+ --df-review-source-popover-subtle: rgba(237, 243, 251, 0.5);
2419
+ --df-review-source-popover-hover: rgba(124, 199, 255, 0.14);
2420
+ position: fixed;
2421
+ z-index: 890;
2422
+ display: grid;
2423
+ width: fit-content;
2424
+ min-width: min(240px, calc(100vw - 24px));
2425
+ max-width: min(440px, calc(100vw - 24px));
2426
+ max-height: 260px;
2427
+ overflow: hidden;
2428
+ border: 1px solid var(--df-review-source-popover-line);
2429
+ border-radius: var(--df-review-radius-md);
2430
+ padding: 8px 6px 6px;
2431
+ color: var(--df-review-source-popover-text);
2432
+ color-scheme: dark;
2433
+ background: rgba(19, 24, 33, 0.96);
2434
+ box-shadow: var(--df-review-shadow-panel);
2435
+ backdrop-filter: blur(10px);
2436
+ }
2437
+
2438
+ .df-review-source-popover-close {
2439
+ position: absolute;
2440
+ top: 5px;
2441
+ right: 5px;
2442
+ z-index: 1;
2443
+ }
2444
+
2445
+ .df-review-source-popover-close button {
2446
+ display: grid;
2447
+ place-items: center;
2448
+ width: 24px;
2449
+ height: 24px;
2450
+ border: 1px solid transparent;
2451
+ border-radius: var(--df-review-radius-sm);
2452
+ padding: 0;
2453
+ color: var(--df-review-source-popover-subtle);
2454
+ background: transparent;
2455
+ font-size: 16px;
2456
+ font-weight: 800;
2457
+ line-height: 1;
2458
+ }
2459
+
2460
+ .df-review-source-popover-close button:hover {
2461
+ border-color: var(--df-review-source-popover-line);
2462
+ color: var(--df-review-source-popover-text);
2463
+ background: var(--df-review-source-popover-hover);
2464
+ }
2465
+
2466
+ .df-review-source-candidate-list {
2467
+ display: grid;
2468
+ gap: 0;
2469
+ max-height: min(220px, calc(100vh - 96px));
2470
+ min-height: 0;
2471
+ overflow-x: hidden;
2472
+ overflow-y: auto;
2473
+ padding-right: 2px;
2474
+ scrollbar-gutter: stable;
2475
+ }
2476
+
2477
+ .df-review-source-candidate {
2478
+ display: grid;
2479
+ width: 100%;
2480
+ min-height: 54px;
2481
+ border: 0;
2482
+ border-radius: var(--df-review-radius-sm);
2483
+ padding: 6px 30px 6px 8px;
2484
+ color: var(--df-review-source-popover-text);
2485
+ background: transparent;
2486
+ text-align: left;
2487
+ }
2488
+
2489
+ .df-review-source-candidate:hover {
2490
+ background: var(--df-review-source-popover-hover);
2491
+ }
2492
+
2493
+ .df-review-source-candidate-main {
2494
+ display: grid;
2495
+ gap: 2px;
2496
+ min-width: 0;
2497
+ }
2498
+
2499
+ .df-review-source-candidate-main strong,
2500
+ .df-review-source-candidate-main small {
2501
+ overflow: hidden;
2502
+ text-overflow: ellipsis;
2503
+ white-space: nowrap;
2504
+ }
2505
+
2506
+ .df-review-source-candidate-main strong {
2507
+ font-size: var(--df-review-font-size-xs);
2508
+ font-weight: 900;
2509
+ }
2510
+
2511
+ .df-review-source-candidate-main span {
2512
+ overflow-wrap: anywhere;
2513
+ color: var(--df-review-source-popover-muted);
2514
+ font-family: var(--df-review-font-mono);
2515
+ font-size: var(--df-review-font-size-2xs);
2516
+ line-height: 1.25;
2517
+ white-space: normal;
2518
+ }
2519
+
2520
+ .df-review-source-candidate-main small {
2521
+ color: var(--df-review-source-popover-subtle);
2522
+ font-family: var(--df-review-font-mono);
2523
+ font-size: var(--df-review-font-size-2xs);
2524
+ }
2525
+
2526
+ .df-review-device-frame {
2527
+ position: relative;
2528
+ box-sizing: border-box;
2529
+ flex: 0 0 auto;
2530
+ }
2531
+
2532
+ .df-review-ruler-corner {
2533
+ position: absolute;
2534
+ left: -26px;
2535
+ top: -26px;
2536
+ width: 26px;
2537
+ height: 26px;
2538
+ z-index: 6;
2539
+ border-right: 1px solid var(--df-review-line-soft);
2540
+ border-bottom: 1px solid var(--df-review-line-soft);
2541
+ background: var(--df-review-color-ruler-surface);
2542
+ }
2543
+
2544
+ .df-review-ruler-gutter {
2545
+ position: absolute;
2546
+ z-index: 6;
2547
+ background: var(--df-review-color-ruler-surface);
2548
+ color: var(--df-review-muted);
2549
+ user-select: none;
2550
+ }
2551
+
2552
+ .df-review-ruler-gutter.is-x {
2553
+ left: 0;
2554
+ right: 0;
2555
+ top: -26px;
2556
+ height: 26px;
2557
+ border-bottom: 1px solid var(--df-review-line-soft);
2558
+ background-image:
2559
+ linear-gradient(
2560
+ to right,
2561
+ var(--df-review-color-ruler-tick-major) 1px,
2562
+ transparent 1px
2563
+ ),
2564
+ linear-gradient(
2565
+ to right,
2566
+ var(--df-review-color-ruler-tick-minor) 1px,
2567
+ transparent 1px
2568
+ );
2569
+ background-size:
2570
+ calc(var(--df-review-ruler-step-x) * 5) 11px,
2571
+ var(--df-review-ruler-step-x) 6px;
2572
+ background-position: left bottom;
2573
+ background-repeat: repeat-x;
2574
+ }
2575
+
2576
+ .df-review-ruler-gutter.is-y {
2577
+ left: -26px;
2578
+ top: 0;
2579
+ bottom: 0;
2580
+ width: 26px;
2581
+ border-right: 1px solid var(--df-review-line-soft);
2582
+ background-image:
2583
+ linear-gradient(
2584
+ to bottom,
2585
+ var(--df-review-color-ruler-tick-major) 1px,
2586
+ transparent 1px
2587
+ ),
2588
+ linear-gradient(
2589
+ to bottom,
2590
+ var(--df-review-color-ruler-tick-minor) 1px,
2591
+ transparent 1px
2592
+ );
2593
+ background-size:
2594
+ 11px calc(var(--df-review-ruler-step-y) * 5),
2595
+ 6px var(--df-review-ruler-step-y);
2596
+ background-position: right top;
2597
+ background-repeat: repeat-y;
2598
+ }
2599
+
2600
+ .df-review-ruler-frame-label {
2601
+ position: absolute;
2602
+ right: 6px;
2603
+ top: 50%;
2604
+ transform: translateY(-50%);
2605
+ display: flex;
2261
2606
  align-items: center;
2262
2607
  gap: 6px;
2263
2608
  padding: 4px 8px;
@@ -2383,7 +2728,7 @@ function ensureReviewShellStyle() {
2383
2728
  .df-review-shell,
2384
2729
  .df-review-shell.is-list-visible {
2385
2730
  grid-template-columns: minmax(0, 1fr) 0 32px;
2386
- grid-template-rows: auto minmax(0, 1fr);
2731
+ grid-template-rows: auto auto minmax(0, 1fr);
2387
2732
  }
2388
2733
 
2389
2734
  .df-review-shell.is-list-visible {
@@ -2439,8 +2784,9 @@ function ensureReviewShellStyle() {
2439
2784
  import {
2440
2785
  useCallback as useCallback11,
2441
2786
  useEffect as useEffect10,
2442
- useRef as useRef4,
2443
- useState as useState8
2787
+ useMemo as useMemo7,
2788
+ useRef as useRef5,
2789
+ useState as useState10
2444
2790
  } from "react";
2445
2791
 
2446
2792
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/createLucideIcon.mjs
@@ -2547,31 +2893,42 @@ var createLucideIcon = (iconName, iconNode) => {
2547
2893
  return Component;
2548
2894
  };
2549
2895
 
2550
- // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/circle-question-mark.mjs
2896
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/bot.mjs
2551
2897
  var __iconNode = [
2898
+ ["path", { d: "M12 8V4H8", key: "hb8ula" }],
2899
+ ["rect", { width: "16", height: "12", x: "4", y: "8", rx: "2", key: "enze0r" }],
2900
+ ["path", { d: "M2 14h2", key: "vft8re" }],
2901
+ ["path", { d: "M20 14h2", key: "4cs60a" }],
2902
+ ["path", { d: "M15 13v2", key: "1xurst" }],
2903
+ ["path", { d: "M9 13v2", key: "rq6x2g" }]
2904
+ ];
2905
+ var Bot = createLucideIcon("bot", __iconNode);
2906
+
2907
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/circle-question-mark.mjs
2908
+ var __iconNode2 = [
2552
2909
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
2553
2910
  ["path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3", key: "1u773s" }],
2554
2911
  ["path", { d: "M12 17h.01", key: "p32p05" }]
2555
2912
  ];
2556
- var CircleQuestionMark = createLucideIcon("circle-question-mark", __iconNode);
2913
+ var CircleQuestionMark = createLucideIcon("circle-question-mark", __iconNode2);
2557
2914
 
2558
2915
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/copy.mjs
2559
- var __iconNode2 = [
2916
+ var __iconNode3 = [
2560
2917
  ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
2561
2918
  ["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
2919
  ];
2563
- var Copy = createLucideIcon("copy", __iconNode2);
2920
+ var Copy = createLucideIcon("copy", __iconNode3);
2564
2921
 
2565
2922
  // 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 = [
2923
+ var __iconNode4 = [
2567
2924
  ["path", { d: "M15 3h6v6", key: "1q9fwt" }],
2568
2925
  ["path", { d: "M10 14 21 3", key: "gplh6r" }],
2569
2926
  ["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
2927
  ];
2571
- var ExternalLink = createLucideIcon("external-link", __iconNode3);
2928
+ var ExternalLink = createLucideIcon("external-link", __iconNode4);
2572
2929
 
2573
2930
  // 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 = [
2931
+ var __iconNode5 = [
2575
2932
  [
2576
2933
  "path",
2577
2934
  {
@@ -2589,10 +2946,10 @@ var __iconNode4 = [
2589
2946
  ],
2590
2947
  ["path", { d: "m2 2 20 20", key: "1ooewy" }]
2591
2948
  ];
2592
- var EyeOff = createLucideIcon("eye-off", __iconNode4);
2949
+ var EyeOff = createLucideIcon("eye-off", __iconNode5);
2593
2950
 
2594
2951
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/eye.mjs
2595
- var __iconNode5 = [
2952
+ var __iconNode6 = [
2596
2953
  [
2597
2954
  "path",
2598
2955
  {
@@ -2602,22 +2959,7 @@ var __iconNode5 = [
2602
2959
  ],
2603
2960
  ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
2604
2961
  ];
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);
2962
+ var Eye = createLucideIcon("eye", __iconNode6);
2621
2963
 
2622
2964
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/grip-vertical.mjs
2623
2965
  var __iconNode7 = [
@@ -2647,16 +2989,24 @@ var __iconNode9 = [
2647
2989
  ];
2648
2990
  var LayoutGrid = createLucideIcon("layout-grid", __iconNode9);
2649
2991
 
2650
- // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/list-filter.mjs
2992
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/link-2.mjs
2651
2993
  var __iconNode10 = [
2994
+ ["path", { d: "M9 17H7A5 5 0 0 1 7 7h2", key: "8i5ue5" }],
2995
+ ["path", { d: "M15 7h2a5 5 0 1 1 0 10h-2", key: "1b9ql8" }],
2996
+ ["line", { x1: "8", x2: "16", y1: "12", y2: "12", key: "1jonct" }]
2997
+ ];
2998
+ var Link2 = createLucideIcon("link-2", __iconNode10);
2999
+
3000
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/list-filter.mjs
3001
+ var __iconNode11 = [
2652
3002
  ["path", { d: "M2 5h20", key: "1fs1ex" }],
2653
3003
  ["path", { d: "M6 12h12", key: "8npq4p" }],
2654
3004
  ["path", { d: "M9 19h6", key: "456am0" }]
2655
3005
  ];
2656
- var ListFilter = createLucideIcon("list-filter", __iconNode10);
3006
+ var ListFilter = createLucideIcon("list-filter", __iconNode11);
2657
3007
 
2658
3008
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/map.mjs
2659
- var __iconNode11 = [
3009
+ var __iconNode12 = [
2660
3010
  [
2661
3011
  "path",
2662
3012
  {
@@ -2667,27 +3017,27 @@ var __iconNode11 = [
2667
3017
  ["path", { d: "M15 5.764v15", key: "1pn4in" }],
2668
3018
  ["path", { d: "M9 3.236v15", key: "1uimfh" }]
2669
3019
  ];
2670
- var Map2 = createLucideIcon("map", __iconNode11);
3020
+ var Map2 = createLucideIcon("map", __iconNode12);
2671
3021
 
2672
3022
  // 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 = [
3023
+ var __iconNode13 = [
2674
3024
  ["path", { d: "M15 3h6v6", key: "1q9fwt" }],
2675
3025
  ["path", { d: "m21 3-7 7", key: "1l2asr" }],
2676
3026
  ["path", { d: "m3 21 7-7", key: "tjx5ai" }],
2677
3027
  ["path", { d: "M9 21H3v-6", key: "wtvkvv" }]
2678
3028
  ];
2679
- var Maximize2 = createLucideIcon("maximize-2", __iconNode12);
3029
+ var Maximize2 = createLucideIcon("maximize-2", __iconNode13);
2680
3030
 
2681
3031
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/monitor.mjs
2682
- var __iconNode13 = [
3032
+ var __iconNode14 = [
2683
3033
  ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2", key: "48i651" }],
2684
3034
  ["line", { x1: "8", x2: "16", y1: "21", y2: "21", key: "1svkeh" }],
2685
3035
  ["line", { x1: "12", x2: "12", y1: "17", y2: "21", key: "vw1qmm" }]
2686
3036
  ];
2687
- var Monitor = createLucideIcon("monitor", __iconNode13);
3037
+ var Monitor = createLucideIcon("monitor", __iconNode14);
2688
3038
 
2689
3039
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/pencil.mjs
2690
- var __iconNode14 = [
3040
+ var __iconNode15 = [
2691
3041
  [
2692
3042
  "path",
2693
3043
  {
@@ -2697,25 +3047,25 @@ var __iconNode14 = [
2697
3047
  ],
2698
3048
  ["path", { d: "m15 5 4 4", key: "1mk7zo" }]
2699
3049
  ];
2700
- var Pencil = createLucideIcon("pencil", __iconNode14);
3050
+ var Pencil = createLucideIcon("pencil", __iconNode15);
2701
3051
 
2702
3052
  // 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 = [
3053
+ var __iconNode16 = [
2704
3054
  ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2", key: "9lu3g6" }]
2705
3055
  ];
2706
- var RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode15);
3056
+ var RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode16);
2707
3057
 
2708
3058
  // 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 = [
3059
+ var __iconNode17 = [
2710
3060
  ["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
3061
  ["path", { d: "M21 3v5h-5", key: "1q7to0" }],
2712
3062
  ["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
3063
  ["path", { d: "M8 16H3v5", key: "1cv678" }]
2714
3064
  ];
2715
- var RefreshCw = createLucideIcon("refresh-cw", __iconNode16);
3065
+ var RefreshCw = createLucideIcon("refresh-cw", __iconNode17);
2716
3066
 
2717
3067
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/ruler.mjs
2718
- var __iconNode17 = [
3068
+ var __iconNode18 = [
2719
3069
  [
2720
3070
  "path",
2721
3071
  {
@@ -2728,19 +3078,19 @@ var __iconNode17 = [
2728
3078
  ["path", { d: "m8.5 6.5 2-2", key: "vc6u1g" }],
2729
3079
  ["path", { d: "m17.5 15.5 2-2", key: "wo5hmg" }]
2730
3080
  ];
2731
- var Ruler = createLucideIcon("ruler", __iconNode17);
3081
+ var Ruler = createLucideIcon("ruler", __iconNode18);
2732
3082
 
2733
3083
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/scan.mjs
2734
- var __iconNode18 = [
3084
+ var __iconNode19 = [
2735
3085
  ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2", key: "aa7l1z" }],
2736
3086
  ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2", key: "4qcy5o" }],
2737
3087
  ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2", key: "6vwrx8" }],
2738
3088
  ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2", key: "ioqczr" }]
2739
3089
  ];
2740
- var Scan = createLucideIcon("scan", __iconNode18);
3090
+ var Scan = createLucideIcon("scan", __iconNode19);
2741
3091
 
2742
3092
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/settings.mjs
2743
- var __iconNode19 = [
3093
+ var __iconNode20 = [
2744
3094
  [
2745
3095
  "path",
2746
3096
  {
@@ -2750,17 +3100,17 @@ var __iconNode19 = [
2750
3100
  ],
2751
3101
  ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
2752
3102
  ];
2753
- var Settings = createLucideIcon("settings", __iconNode19);
3103
+ var Settings = createLucideIcon("settings", __iconNode20);
2754
3104
 
2755
3105
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/smartphone.mjs
2756
- var __iconNode20 = [
3106
+ var __iconNode21 = [
2757
3107
  ["rect", { width: "14", height: "20", x: "5", y: "2", rx: "2", ry: "2", key: "1yt0o3" }],
2758
3108
  ["path", { d: "M12 18h.01", key: "mhygvu" }]
2759
3109
  ];
2760
- var Smartphone = createLucideIcon("smartphone", __iconNode20);
3110
+ var Smartphone = createLucideIcon("smartphone", __iconNode21);
2761
3111
 
2762
3112
  // 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 = [
3113
+ var __iconNode22 = [
2764
3114
  [
2765
3115
  "path",
2766
3116
  {
@@ -2770,10 +3120,10 @@ var __iconNode21 = [
2770
3120
  ],
2771
3121
  ["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
3122
  ];
2773
- var SquareMousePointer = createLucideIcon("square-mouse-pointer", __iconNode21);
3123
+ var SquareMousePointer = createLucideIcon("square-mouse-pointer", __iconNode22);
2774
3124
 
2775
3125
  // 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 = [
3126
+ var __iconNode23 = [
2777
3127
  [
2778
3128
  "path",
2779
3129
  {
@@ -2783,24 +3133,15 @@ var __iconNode22 = [
2783
3133
  ],
2784
3134
  ["path", { d: "M15 3v5a1 1 0 0 0 1 1h5", key: "6s6qgf" }]
2785
3135
  ];
2786
- var StickyNote = createLucideIcon("sticky-note", __iconNode22);
3136
+ var StickyNote = createLucideIcon("sticky-note", __iconNode23);
2787
3137
 
2788
3138
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/upload.mjs
2789
- var __iconNode23 = [
3139
+ var __iconNode24 = [
2790
3140
  ["path", { d: "M12 3v12", key: "1x0j5s" }],
2791
3141
  ["path", { d: "m17 8-5-5-5 5", key: "7q97r8" }],
2792
3142
  ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }]
2793
3143
  ];
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);
3144
+ var Upload = createLucideIcon("upload", __iconNode24);
2804
3145
 
2805
3146
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/x.mjs
2806
3147
  var __iconNode25 = [
@@ -2844,6 +3185,45 @@ var normalizeTarget = (value, reviewPathPrefix = DEFAULT_REVIEW_PATH_PREFIX) =>
2844
3185
  const reviewPrefix = normalizeReviewPathPrefix(reviewPathPrefix);
2845
3186
  return normalized === reviewPrefix || normalized.startsWith(`${reviewPrefix}/`) ? "/" : normalized;
2846
3187
  };
3188
+ var parseReviewAddressInput = (value, reviewPathPrefix = DEFAULT_REVIEW_PATH_PREFIX) => {
3189
+ const raw = value.trim();
3190
+ if (!raw) return { target: "/" };
3191
+ const parsedUrl = parseSameOriginUrl(raw);
3192
+ if (!parsedUrl) {
3193
+ return { target: normalizeTarget(raw, reviewPathPrefix) };
3194
+ }
3195
+ const reviewPrefix = normalizeReviewPathPrefix(reviewPathPrefix);
3196
+ const isReviewUrl = parsedUrl.pathname === reviewPrefix || parsedUrl.pathname.startsWith(`${reviewPrefix}/`);
3197
+ if (!isReviewUrl) {
3198
+ return {
3199
+ target: normalizeTarget(parsedUrl.pathname, reviewPathPrefix)
3200
+ };
3201
+ }
3202
+ const source = parsedUrl.searchParams.get("source")?.trim();
3203
+ return {
3204
+ height: getPositiveParamNumber(parsedUrl.searchParams, "h"),
3205
+ itemId: parsedUrl.searchParams.get("item"),
3206
+ source: source ? source : void 0,
3207
+ target: normalizeTarget(
3208
+ parsedUrl.searchParams.get("target") ?? "/",
3209
+ reviewPathPrefix
3210
+ ),
3211
+ width: getPositiveParamNumber(parsedUrl.searchParams, "w")
3212
+ };
3213
+ };
3214
+ function parseSameOriginUrl(value) {
3215
+ if (typeof window === "undefined") return null;
3216
+ try {
3217
+ const url = new URL(value, window.location.origin);
3218
+ return url.origin === window.location.origin ? url : null;
3219
+ } catch {
3220
+ return null;
3221
+ }
3222
+ }
3223
+ function getPositiveParamNumber(params, name) {
3224
+ const value = Number(params.get(name));
3225
+ return Number.isFinite(value) && value > 0 ? value : void 0;
3226
+ }
2847
3227
  var getInitialTarget = (reviewPathPrefix = DEFAULT_REVIEW_PATH_PREFIX) => {
2848
3228
  if (typeof window === "undefined") return "/";
2849
3229
  const target = new URLSearchParams(window.location.search).get("target");
@@ -2884,6 +3264,10 @@ var updateShellUrl = (target, size, source) => {
2884
3264
  window.history.replaceState(null, "", `${url.pathname}${url.search}`);
2885
3265
  };
2886
3266
  var updateShellUrlForItem = (target, size, itemId, source) => {
3267
+ const url = getShellUrlForItem(target, size, itemId, source);
3268
+ window.history.replaceState(null, "", `${url.pathname}${url.search}`);
3269
+ };
3270
+ var getShellUrlForItem = (target, size, itemId, source) => {
2887
3271
  const url = new URL(window.location.href);
2888
3272
  url.searchParams.set("target", target);
2889
3273
  url.searchParams.set("w", String(size.width));
@@ -2894,7 +3278,7 @@ var updateShellUrlForItem = (target, size, itemId, source) => {
2894
3278
  } else {
2895
3279
  url.searchParams.delete("source");
2896
3280
  }
2897
- window.history.replaceState(null, "", `${url.pathname}${url.search}`);
3281
+ return url;
2898
3282
  };
2899
3283
  var getInitialItemId = () => {
2900
3284
  if (typeof window === "undefined") return null;
@@ -2976,7 +3360,9 @@ var toReviewViewportPresets = (presets) => presets.map((preset) => ({
2976
3360
  label: preset.label,
2977
3361
  width: preset.width,
2978
3362
  height: preset.height,
2979
- scope: getViewportPresetKind(preset)
3363
+ scope: getViewportPresetKind(preset),
3364
+ designWidth: preset.designWidth,
3365
+ designHeight: preset.designHeight
2980
3366
  }));
2981
3367
  var getIsFigmaOverlayAvailable = (preset) => {
2982
3368
  const kind = getViewportPresetKind(preset);
@@ -3450,11 +3836,40 @@ var ReviewSettingsModal = ({
3450
3836
  );
3451
3837
  };
3452
3838
 
3839
+ // src/react-shell/sitemap/modal.tsx
3840
+ import {
3841
+ useMemo as useMemo2,
3842
+ useState
3843
+ } from "react";
3844
+
3453
3845
  // src/react-shell/sitemap/tree.ts
3454
- var EMPTY_SITEMAP_QA_COUNT = {
3846
+ var WORKFLOW_STATUSES = [
3847
+ "todo",
3848
+ "doing",
3849
+ "review",
3850
+ "hold",
3851
+ "done"
3852
+ ];
3853
+ var createEmptySitemapQaCount = () => ({
3854
+ total: 0,
3855
+ remaining: 0,
3455
3856
  local: 0,
3456
- remote: 0
3457
- };
3857
+ remote: 0,
3858
+ status: {
3859
+ todo: 0,
3860
+ doing: 0,
3861
+ review: 0,
3862
+ hold: 0,
3863
+ done: 0
3864
+ },
3865
+ scope: {},
3866
+ viewport: {}
3867
+ });
3868
+ var createSitemapViewportColumn = (preset, index) => ({
3869
+ key: `${index}:${preset.width}x${preset.height}`,
3870
+ label: preset.label,
3871
+ title: `${preset.label} ${preset.width}x${preset.height}`
3872
+ });
3458
3873
  var normalizeSitemapHref = (href) => {
3459
3874
  const [path = "/"] = href.split(/[?#]/);
3460
3875
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
@@ -3479,10 +3894,45 @@ var mergeSitemapUsers = (users) => {
3479
3894
  return Array.from(userByKey.values());
3480
3895
  };
3481
3896
  var addSitemapQaCounts = (first, second) => ({
3897
+ total: first.total + second.total,
3898
+ remaining: first.remaining + second.remaining,
3482
3899
  local: first.local + second.local,
3483
- remote: first.remote + second.remote
3900
+ remote: first.remote + second.remote,
3901
+ status: WORKFLOW_STATUSES.reduce(
3902
+ (statusCounts, status) => ({
3903
+ ...statusCounts,
3904
+ [status]: first.status[status] + second.status[status]
3905
+ }),
3906
+ {}
3907
+ ),
3908
+ scope: Array.from(
3909
+ /* @__PURE__ */ new Set([
3910
+ ...Object.keys(first.scope),
3911
+ ...Object.keys(second.scope)
3912
+ ])
3913
+ ).reduce(
3914
+ (scopeCounts, scope) => ({
3915
+ ...scopeCounts,
3916
+ [scope]: (first.scope[scope] ?? 0) + (second.scope[scope] ?? 0)
3917
+ }),
3918
+ {}
3919
+ ),
3920
+ viewport: Array.from(
3921
+ /* @__PURE__ */ new Set([...Object.keys(first.viewport), ...Object.keys(second.viewport)])
3922
+ ).reduce(
3923
+ (viewportCounts, viewportKey) => ({
3924
+ ...viewportCounts,
3925
+ [viewportKey]: {
3926
+ total: (first.viewport[viewportKey]?.total ?? 0) + (second.viewport[viewportKey]?.total ?? 0),
3927
+ remaining: (first.viewport[viewportKey]?.remaining ?? 0) + (second.viewport[viewportKey]?.remaining ?? 0)
3928
+ }
3929
+ }),
3930
+ {}
3931
+ )
3484
3932
  });
3485
- var createSitemapRows = (pages, activeRoute, pageQaCounts, pagePresenceUsers, getPageTarget) => {
3933
+ var createSitemapRows = (pages, activeRoute, pageQaCounts, pagePresenceUsers, getPageTarget, options = {}) => {
3934
+ const sortKey = options.sortKey ?? "page";
3935
+ const sortDirection = options.sortDirection ?? "asc";
3486
3936
  const root = createSitemapNode("/", "/", false);
3487
3937
  pages.forEach((page) => {
3488
3938
  const pageHref = page.href.startsWith("/") ? page.href : `/${page.href}`;
@@ -3509,60 +3959,89 @@ var createSitemapRows = (pages, activeRoute, pageQaCounts, pagePresenceUsers, ge
3509
3959
  });
3510
3960
  });
3511
3961
  const getDirectCount = (node) => {
3512
- if (!node.isPage) return EMPTY_SITEMAP_QA_COUNT;
3513
- return pageQaCounts.get(getPageTarget(node.href)) ?? EMPTY_SITEMAP_QA_COUNT;
3962
+ if (!node.isPage) return createEmptySitemapQaCount();
3963
+ return pageQaCounts.get(getPageTarget(node.href)) ?? createEmptySitemapQaCount();
3514
3964
  };
3515
3965
  const getDirectUsers = (node) => {
3516
3966
  if (!node.isPage) return [];
3517
3967
  return pagePresenceUsers.get(getPageTarget(node.href)) ?? [];
3518
3968
  };
3519
- const rows = [];
3520
- const appendNodeRows = (node, depth, ancestorLastList, isLastNode) => {
3521
- const children = Array.from(node.children.values());
3969
+ const createNodeSummary = (node) => {
3522
3970
  const directCount = getDirectCount(node);
3523
3971
  const directUsers = getDirectUsers(node);
3524
- let rowIndex = null;
3525
- if (node.isPage || depth > 0) {
3526
- const prefix = depth === 0 ? "" : `${ancestorLastList.map((isLast) => isLast ? " " : "\u2502 ").join("")}${isLastNode ? "\u2514\u2500 " : "\u251C\u2500 "}`;
3527
- const pageTarget = node.isPage ? getPageTarget(node.href) : null;
3528
- rowIndex = rows.length;
3529
- rows.push({
3530
- href: node.href,
3531
- label: node.label,
3532
- prefix,
3533
- isPage: node.isPage,
3534
- isActive: pageTarget === activeRoute,
3535
- qaCount: directCount,
3536
- users: directUsers
3537
- });
3538
- }
3972
+ const children = Array.from(node.children.values()).map(createNodeSummary);
3539
3973
  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: [] }
3974
+ (aggregate, child) => ({
3975
+ count: addSitemapQaCounts(aggregate.count, child.count),
3976
+ users: mergeSitemapUsers([...aggregate.users, ...child.users])
3977
+ }),
3978
+ {
3979
+ count: createEmptySitemapQaCount(),
3980
+ users: []
3981
+ }
3553
3982
  );
3554
- if (rowIndex !== null && !node.isPage) {
3555
- rows[rowIndex] = {
3556
- ...rows[rowIndex],
3557
- qaCount: childAggregate.count,
3558
- users: childAggregate.users
3559
- };
3560
- }
3561
3983
  return {
3984
+ node,
3985
+ directCount,
3986
+ directUsers,
3562
3987
  count: node.isPage ? addSitemapQaCounts(directCount, childAggregate.count) : childAggregate.count,
3563
- users: mergeSitemapUsers([...directUsers, ...childAggregate.users])
3988
+ users: mergeSitemapUsers([...directUsers, ...childAggregate.users]),
3989
+ children
3564
3990
  };
3565
3991
  };
3992
+ const getSortValue = (summary) => {
3993
+ if (sortKey === "page") return summary.node.label;
3994
+ if (sortKey === "total") return summary.count.remaining;
3995
+ if (sortKey === "review") return summary.count.status.review;
3996
+ if (sortKey === "hold") return summary.count.status.hold;
3997
+ if (sortKey === "online") return summary.users.length;
3998
+ if (sortKey.startsWith("viewport:")) {
3999
+ const viewportKey = sortKey.slice("viewport:".length);
4000
+ return summary.count.viewport[viewportKey]?.remaining ?? 0;
4001
+ }
4002
+ return 0;
4003
+ };
4004
+ const sortSummaries = (summaries) => {
4005
+ return [...summaries].sort((a, b) => {
4006
+ const firstValue = getSortValue(a);
4007
+ const secondValue = getSortValue(b);
4008
+ const valueDiff = typeof firstValue === "string" && typeof secondValue === "string" ? firstValue.localeCompare(secondValue) : Number(firstValue) - Number(secondValue);
4009
+ if (valueDiff !== 0) {
4010
+ return sortDirection === "asc" ? valueDiff : -valueDiff;
4011
+ }
4012
+ const totalDiff = b.count.remaining - a.count.remaining;
4013
+ if (totalDiff !== 0) return totalDiff;
4014
+ return a.node.label.localeCompare(b.node.label);
4015
+ });
4016
+ };
4017
+ const rows = [];
4018
+ const appendSummaryRows = (summary, depth, ancestorLastList, isLastNode) => {
4019
+ const { node } = summary;
4020
+ const rowCount = node.isPage ? summary.directCount : summary.count;
4021
+ const rowUsers = node.isPage ? summary.directUsers : summary.users;
4022
+ if (node.isPage || depth > 0) {
4023
+ const prefix = depth === 0 ? "" : `${ancestorLastList.map((isLast) => isLast ? " " : "\u2502 ").join("")}${isLastNode ? "\u2514\u2500 " : "\u251C\u2500 "}`;
4024
+ const pageTarget = node.isPage ? getPageTarget(node.href) : null;
4025
+ rows.push({
4026
+ href: node.href,
4027
+ label: node.label,
4028
+ prefix,
4029
+ isPage: node.isPage,
4030
+ isActive: pageTarget === activeRoute,
4031
+ qaCount: rowCount,
4032
+ users: rowUsers
4033
+ });
4034
+ }
4035
+ const visibleChildren = sortSummaries(summary.children);
4036
+ visibleChildren.forEach((child, childIndex) => {
4037
+ appendSummaryRows(
4038
+ child,
4039
+ depth + 1,
4040
+ depth === 0 ? [] : [...ancestorLastList, isLastNode],
4041
+ childIndex === visibleChildren.length - 1
4042
+ );
4043
+ });
4044
+ };
3566
4045
  if (root.isPage) {
3567
4046
  const directCount = getDirectCount(root);
3568
4047
  const directUsers = getDirectUsers(root);
@@ -3576,30 +4055,83 @@ var createSitemapRows = (pages, activeRoute, pageQaCounts, pagePresenceUsers, ge
3576
4055
  users: directUsers
3577
4056
  });
3578
4057
  }
3579
- Array.from(root.children.values()).forEach((node, index, siblings) => {
3580
- appendNodeRows(node, 1, [], index === siblings.length - 1);
4058
+ const rootSummaries = sortSummaries(
4059
+ Array.from(root.children.values()).map(createNodeSummary)
4060
+ );
4061
+ rootSummaries.forEach((summary, index, siblings) => {
4062
+ appendSummaryRows(summary, 1, [], index === siblings.length - 1);
3581
4063
  });
3582
4064
  return rows;
3583
4065
  };
3584
4066
 
3585
4067
  // src/react-shell/sitemap/modal.tsx
3586
4068
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
4069
+ var getNextSortDirection = (current, key) => {
4070
+ if (current.key !== key) return key === "page" ? "asc" : "desc";
4071
+ return current.direction === "desc" ? "asc" : "desc";
4072
+ };
4073
+ var getSortIndicator = (sort, key) => {
4074
+ if (sort.key !== key) return "";
4075
+ return sort.direction === "desc" ? "\u2193" : "\u2191";
4076
+ };
4077
+ var mergePresenceUsers = (users) => {
4078
+ const userByKey = /* @__PURE__ */ new Map();
4079
+ users.forEach((user) => {
4080
+ const key = user.sessionId || user.userId;
4081
+ const currentUser = userByKey.get(key);
4082
+ if (!currentUser || Date.parse(user.updatedAt) >= Date.parse(currentUser.updatedAt)) {
4083
+ userByKey.set(key, user);
4084
+ }
4085
+ });
4086
+ return Array.from(userByKey.values());
4087
+ };
3587
4088
  var SitemapModal = ({
3588
4089
  pages,
3589
4090
  activeRoute,
4091
+ allQaCount,
4092
+ isAllQaVisible,
3590
4093
  pageQaCounts,
3591
4094
  pagePresenceUsers,
3592
4095
  getPageTarget,
3593
4096
  onClose,
4097
+ onSelectAllQa,
3594
4098
  onSelectPage
3595
4099
  }) => {
4100
+ const [sort, setSort] = useState({
4101
+ key: "total",
4102
+ direction: "desc"
4103
+ });
4104
+ const allQaUsers = useMemo2(
4105
+ () => mergePresenceUsers(Array.from(pagePresenceUsers.values()).flat()),
4106
+ [pagePresenceUsers]
4107
+ );
3596
4108
  const sitemapRows = createSitemapRows(
3597
4109
  pages,
3598
4110
  activeRoute,
3599
4111
  pageQaCounts,
3600
4112
  pagePresenceUsers,
3601
- getPageTarget
4113
+ getPageTarget,
4114
+ {
4115
+ sortKey: sort.key,
4116
+ sortDirection: sort.direction
4117
+ }
3602
4118
  );
4119
+ const gridStyle = {
4120
+ "--df-review-sitemap-grid-template": "minmax(190px, 1fr) 74px 78px 64px minmax(108px, 160px)"
4121
+ };
4122
+ const sortHeaders = [
4123
+ { key: "page", label: "", title: "Page", className: "is-page" },
4124
+ { key: "total", label: "Total", title: "Remaining total" },
4125
+ { key: "review", label: "Review" },
4126
+ { key: "hold", label: "Hold" },
4127
+ { key: "online", label: "Online", className: "is-online" }
4128
+ ];
4129
+ const setSortKey = (key) => {
4130
+ setSort((current) => ({
4131
+ key,
4132
+ direction: getNextSortDirection(current, key)
4133
+ }));
4134
+ };
3603
4135
  return /* @__PURE__ */ jsxs3(
3604
4136
  "div",
3605
4137
  {
@@ -3623,48 +4155,65 @@ var SitemapModal = ({
3623
4155
  /* @__PURE__ */ jsx3("strong", { children: "Sitemap" }),
3624
4156
  /* @__PURE__ */ jsxs3("span", { children: [
3625
4157
  pages.length,
3626
- " pages"
4158
+ " pages \xB7 ",
4159
+ allQaCount.remaining,
4160
+ " remaining \xB7",
4161
+ " ",
4162
+ allQaCount.status.review,
4163
+ " review \xB7 ",
4164
+ allQaCount.status.hold,
4165
+ " hold"
3627
4166
  ] })
3628
4167
  ] }),
3629
4168
  /* @__PURE__ */ jsx3("button", { "aria-label": "Close sitemap", type: "button", onClick: onClose, children: "x" })
3630
4169
  ] }),
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
- ] }),
4170
+ /* @__PURE__ */ jsxs3("div", { className: "df-review-sitemap-list", style: gridStyle, children: [
4171
+ /* @__PURE__ */ jsx3("div", { className: "df-review-sitemap-table-head", role: "row", children: sortHeaders.map((header) => /* @__PURE__ */ jsxs3(
4172
+ "button",
4173
+ {
4174
+ "aria-label": `Sort sitemap by ${header.title ?? header.label}`,
4175
+ className: [
4176
+ "df-review-sitemap-sort",
4177
+ header.className ?? "",
4178
+ sort.key === header.key ? "is-active" : ""
4179
+ ].filter(Boolean).join(" "),
4180
+ title: header.title ?? header.label,
4181
+ type: "button",
4182
+ onClick: () => setSortKey(header.key),
4183
+ children: [
4184
+ /* @__PURE__ */ jsx3(
4185
+ "span",
4186
+ {
4187
+ "aria-hidden": "true",
4188
+ className: "df-review-sitemap-sort-indicator",
4189
+ children: getSortIndicator(sort, header.key)
4190
+ }
4191
+ ),
4192
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-sort-label", children: header.label })
4193
+ ]
4194
+ },
4195
+ header.key
4196
+ )) }),
3638
4197
  sitemapRows.map((row) => {
3639
4198
  const rowClassName = [
3640
4199
  "df-review-sitemap-row",
3641
4200
  row.isPage ? "is-page" : "is-folder",
3642
4201
  row.isActive ? "is-active" : ""
3643
4202
  ].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
- ] });
4203
+ const rowContent = /* @__PURE__ */ jsx3(
4204
+ SitemapRowContent,
4205
+ {
4206
+ label: row.label,
4207
+ prefix: row.prefix,
4208
+ qaCount: row.qaCount,
4209
+ users: row.users
4210
+ }
4211
+ );
3663
4212
  if (!row.isPage) {
3664
4213
  return /* @__PURE__ */ jsx3(
3665
4214
  "div",
3666
4215
  {
3667
- "aria-label": `${row.href} group / local ${row.qaCount.local} QA / remote ${row.qaCount.remote} QA / ${row.users.length} online`,
4216
+ "aria-label": `${row.href} group / ${row.qaCount.remaining} remaining / ${row.qaCount.status.review} review / ${row.qaCount.status.hold} hold / ${row.users.length} online`,
3668
4217
  className: rowClassName,
3669
4218
  role: "row",
3670
4219
  children: rowContent
@@ -3675,7 +4224,7 @@ var SitemapModal = ({
3675
4224
  return /* @__PURE__ */ jsx3(
3676
4225
  "button",
3677
4226
  {
3678
- "aria-label": `${row.href} / local ${row.qaCount.local} QA / remote ${row.qaCount.remote} QA / ${row.users.length} online`,
4227
+ "aria-label": `${row.href} / ${row.qaCount.remaining} remaining / ${row.qaCount.status.review} review / ${row.qaCount.status.hold} hold / ${row.users.length} online`,
3679
4228
  className: rowClassName,
3680
4229
  type: "button",
3681
4230
  onClick: () => onSelectPage(row.href),
@@ -3683,25 +4232,102 @@ var SitemapModal = ({
3683
4232
  },
3684
4233
  row.href
3685
4234
  );
3686
- })
4235
+ }),
4236
+ /* @__PURE__ */ jsx3(
4237
+ "button",
4238
+ {
4239
+ "aria-label": `All QA / ${allQaCount.remaining} remaining / ${allQaCount.status.review} review / ${allQaCount.status.hold} hold`,
4240
+ className: `df-review-sitemap-row is-summary${isAllQaVisible ? " is-active" : ""}`,
4241
+ type: "button",
4242
+ onClick: onSelectAllQa,
4243
+ children: /* @__PURE__ */ jsx3(
4244
+ SitemapRowContent,
4245
+ {
4246
+ label: "",
4247
+ prefix: "",
4248
+ qaCount: allQaCount,
4249
+ users: allQaUsers
4250
+ }
4251
+ )
4252
+ }
4253
+ )
3687
4254
  ] })
3688
4255
  ] })
3689
4256
  ]
3690
4257
  }
3691
4258
  );
3692
4259
  };
4260
+ var SitemapRowContent = ({
4261
+ label,
4262
+ prefix,
4263
+ qaCount,
4264
+ users
4265
+ }) => /* @__PURE__ */ jsxs3(Fragment, { children: [
4266
+ /* @__PURE__ */ jsxs3("span", { className: "df-review-sitemap-path", children: [
4267
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-tree-prefix", children: prefix }),
4268
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-label", children: label })
4269
+ ] }),
4270
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-cell is-total", children: /* @__PURE__ */ jsx3("strong", { children: qaCount.remaining }) }),
4271
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-cell is-review", children: qaCount.status.review }),
4272
+ /* @__PURE__ */ jsx3("span", { className: "df-review-sitemap-cell is-hold", children: qaCount.status.hold }),
4273
+ /* @__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(
4274
+ "span",
4275
+ {
4276
+ className: "df-review-sitemap-user",
4277
+ style: {
4278
+ "--df-review-presence-color": user.color
4279
+ },
4280
+ children: user.userId
4281
+ },
4282
+ user.sessionId
4283
+ )) }) : null })
4284
+ ] });
4285
+
4286
+ // src/react-shell/figma.ts
4287
+ function getTargetFigmaFrameConfig(targetWindow) {
4288
+ try {
4289
+ const config = targetWindow?.__figma;
4290
+ if (!config || typeof config !== "object") return null;
4291
+ const desktopNodeId = normalizeFigmaNodeValue(config.desktopNodeId);
4292
+ const mobileNodeId = normalizeFigmaNodeValue(config.mobileNodeId);
4293
+ if (!desktopNodeId && !mobileNodeId) return null;
4294
+ return {
4295
+ desktopNodeId,
4296
+ mobileNodeId
4297
+ };
4298
+ } catch {
4299
+ return null;
4300
+ }
4301
+ }
4302
+ function getFigmaFrameUrl(config, preset) {
4303
+ if (!config) return null;
4304
+ const kind = getViewportPresetKind(preset);
4305
+ const value = kind === "mobile" ? config.mobileNodeId : config.desktopNodeId;
4306
+ return value ? createFigmaFrameUrl(value) : null;
4307
+ }
4308
+ function normalizeFigmaNodeValue(value) {
4309
+ return typeof value === "string" ? value.trim() || void 0 : void 0;
4310
+ }
4311
+ function createFigmaFrameUrl(value) {
4312
+ const [fileKey, nodeId] = value.split("->").map((part) => part.trim());
4313
+ if (!fileKey || !nodeId) return null;
4314
+ const urlNodeId = encodeURIComponent(nodeId.replace(/:/g, "-"));
4315
+ return `https://www.figma.com/design/${encodeURIComponent(
4316
+ fileKey
4317
+ )}?node-id=${urlNodeId}`;
4318
+ }
3693
4319
 
3694
4320
  // src/react-shell/qa/item.edit.modal.tsx
3695
- import { useEffect, useState } from "react";
4321
+ import { useEffect, useState as useState2 } from "react";
3696
4322
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3697
4323
  var QaItemEditModal = ({
3698
4324
  item,
3699
4325
  onClose,
3700
4326
  onSave
3701
4327
  }) => {
3702
- const [commentDraft, setCommentDraft] = useState(item.comment);
3703
- const [error, setError] = useState("");
3704
- const [isSaving, setIsSaving] = useState(false);
4328
+ const [commentDraft, setCommentDraft] = useState2(item.comment);
4329
+ const [error, setError] = useState2("");
4330
+ const [isSaving, setIsSaving] = useState2(false);
3705
4331
  useEffect(() => {
3706
4332
  setCommentDraft(item.comment);
3707
4333
  setError("");
@@ -4053,88 +4679,6 @@ var ReviewItemModeIcon = ({
4053
4679
  return /* @__PURE__ */ jsx7(StickyNote, { "aria-hidden": "true" });
4054
4680
  };
4055
4681
 
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
4682
  // src/react-shell/qa/item.card.tsx
4139
4683
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
4140
4684
  var formatItemCardDate = (value) => {
@@ -4157,10 +4701,10 @@ var QaItemCard = ({
4157
4701
  remoteAdapterEntry,
4158
4702
  copiedPromptKey,
4159
4703
  selectedItemId,
4160
- sourceRoot,
4161
4704
  onChangeItemStatus,
4162
4705
  onClearSelectedItem,
4163
4706
  onRemoveItem,
4707
+ onCopyItemLink,
4164
4708
  onCopyItemPrompt,
4165
4709
  onEditItem,
4166
4710
  onRestoreReviewItem,
@@ -4175,13 +4719,14 @@ var QaItemCard = ({
4175
4719
  const itemComment = item.comment.trim() || getItemTitle(item);
4176
4720
  const itemAuthor = item.createdBy?.trim();
4177
4721
  const promptCopyKey = `qa:${item.id}`;
4722
+ const linkCopyKey = `link:${item.id}`;
4178
4723
  const isPromptCopied = copiedPromptKey === promptCopyKey;
4724
+ const isLinkCopied = copiedPromptKey === linkCopyKey;
4179
4725
  const statusOptions = activeAdapterEntry.statusOptions;
4180
4726
  const isActive = item.id === selectedItemId;
4181
4727
  const canUpdateStatus = Boolean(activeAdapterEntry.updateStatus) && statusOptions.length > 0 && !isSubmitting;
4182
4728
  const canEditItem = activeAdapterEntry.canUpdate && !isSubmitting;
4183
4729
  const itemMeta = [formatItemCardDate(item.createdAt), itemAuthor].filter(Boolean).join(" | ");
4184
- const sourceOpenUrl = getSourceOpenUrl(item.anchor?.source, sourceRoot);
4185
4730
  return /* @__PURE__ */ jsxs6(
4186
4731
  "article",
4187
4732
  {
@@ -4233,27 +4778,15 @@ var QaItemCard = ({
4233
4778
  children: isOverlayVisible ? /* @__PURE__ */ jsx8(Eye, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx8(EyeOff, { "aria-hidden": "true" })
4234
4779
  }
4235
4780
  ),
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
4781
  /* @__PURE__ */ jsx8(
4249
4782
  "button",
4250
4783
  {
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",
4784
+ "aria-label": isLinkCopied ? "Copied QA link" : "Copy QA link",
4785
+ className: `df-review-item-link-copy${isLinkCopied ? " is-copied" : ""}`,
4786
+ title: isLinkCopied ? "Copied QA link" : "Copy QA link",
4254
4787
  type: "button",
4255
- onClick: () => onCopyItemPrompt(numberedItem),
4256
- children: /* @__PURE__ */ jsx8(Copy, { "aria-hidden": "true" })
4788
+ onClick: () => onCopyItemLink(numberedItem),
4789
+ children: /* @__PURE__ */ jsx8(Link2, { "aria-hidden": "true" })
4257
4790
  }
4258
4791
  ),
4259
4792
  canEditItem && /* @__PURE__ */ jsx8(
@@ -4291,6 +4824,24 @@ var QaItemCard = ({
4291
4824
  onChangeItemStatus
4292
4825
  }
4293
4826
  ),
4827
+ /* @__PURE__ */ jsx8(
4828
+ "div",
4829
+ {
4830
+ className: "df-review-item-prompt-actions",
4831
+ onClick: (event) => event.stopPropagation(),
4832
+ children: /* @__PURE__ */ jsx8(
4833
+ "button",
4834
+ {
4835
+ "aria-label": isPromptCopied ? "Copied QA prompt" : "Copy QA prompt",
4836
+ className: `df-review-item-action-button df-review-item-prompt-copy${isPromptCopied ? " is-copied" : ""}`,
4837
+ title: isPromptCopied ? "Copied QA prompt" : "Copy QA prompt",
4838
+ type: "button",
4839
+ onClick: () => onCopyItemPrompt(numberedItem),
4840
+ children: isPromptCopied ? /* @__PURE__ */ jsx8(Copy, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx8(Bot, { "aria-hidden": "true" })
4841
+ }
4842
+ )
4843
+ }
4844
+ ),
4294
4845
  /* @__PURE__ */ jsx8(
4295
4846
  QaItemRemoteActions,
4296
4847
  {
@@ -4309,81 +4860,57 @@ var QaItemCard = ({
4309
4860
  );
4310
4861
  };
4311
4862
 
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
4863
  // src/react-shell/qa/panel.header.tsx
4343
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
4864
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
4344
4865
  var QaPanelHeader = ({
4345
4866
  activeItemCount,
4346
- currentPagePresenceUsers,
4867
+ activeRemainingItemCount,
4347
4868
  filteredItemCount,
4869
+ isAllQaVisible,
4348
4870
  label,
4349
- presenceSessionId,
4350
4871
  qaFilter,
4351
4872
  qaFilterCounts,
4873
+ qaStatusFilter,
4874
+ qaStatusFilterCounts,
4352
4875
  showSourceSelect,
4353
4876
  source,
4354
4877
  sourceEntries,
4878
+ statusOptions,
4355
4879
  onChangeReviewSource,
4356
4880
  onQaFilterChange,
4881
+ onQaStatusFilterChange,
4357
4882
  onRefreshReviewData
4358
4883
  }) => {
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(
4884
+ const statusFilterOptions = getStatusFilterOptions(statusOptions);
4885
+ const hasActiveFilter = qaFilter !== "all" || qaStatusFilter !== "all";
4886
+ return /* @__PURE__ */ jsxs7("div", { className: "df-review-list-header", children: [
4887
+ /* @__PURE__ */ jsxs7("div", { className: "df-review-list-toolbar", children: [
4888
+ /* @__PURE__ */ jsxs7("div", { className: "df-review-list-controls", children: [
4889
+ showSourceSelect && /* @__PURE__ */ jsx9(
4363
4890
  "select",
4364
4891
  {
4365
4892
  "aria-label": "QA source",
4366
4893
  className: "df-review-source-select",
4367
4894
  value: source,
4368
4895
  onChange: (event) => onChangeReviewSource(event.currentTarget.value),
4369
- children: sourceEntries.map((entry) => /* @__PURE__ */ jsx10("option", { value: entry.label, children: entry.label }, entry.label))
4896
+ children: sourceEntries.map((entry) => /* @__PURE__ */ jsx9("option", { value: entry.label, children: entry.label }, entry.label))
4370
4897
  }
4371
4898
  ),
4372
- /* @__PURE__ */ jsx10(
4899
+ /* @__PURE__ */ jsx9(
4373
4900
  "button",
4374
4901
  {
4375
4902
  "aria-label": "Refresh QA",
4376
4903
  className: "df-review-source-refresh",
4377
4904
  type: "button",
4378
4905
  onClick: () => void onRefreshReviewData(),
4379
- children: /* @__PURE__ */ jsx10(RefreshCw, { "aria-hidden": "true" })
4906
+ children: /* @__PURE__ */ jsx9(RefreshCw, { "aria-hidden": "true" })
4380
4907
  }
4381
4908
  )
4382
4909
  ] }),
4383
- /* @__PURE__ */ jsx10("div", { className: "df-review-filter-tabs", "aria-label": "QA filters", children: REVIEW_QA_FILTERS.map((filter) => {
4910
+ /* @__PURE__ */ jsx9("div", { className: "df-review-filter-tabs", "aria-label": "QA filters", children: REVIEW_QA_FILTERS.map((filter) => {
4384
4911
  const count = qaFilterCounts.get(filter.key) ?? 0;
4385
4912
  const isActive = qaFilter === filter.key;
4386
- return /* @__PURE__ */ jsx10(
4913
+ return /* @__PURE__ */ jsx9(
4387
4914
  "button",
4388
4915
  {
4389
4916
  "aria-label": `${filter.label} QA (${count})`,
@@ -4391,86 +4918,108 @@ var QaPanelHeader = ({
4391
4918
  className: `df-review-filter-tab${isActive ? " is-active" : ""}`,
4392
4919
  type: "button",
4393
4920
  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" }) })
4921
+ 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
4922
  },
4396
4923
  filter.key
4397
4924
  );
4398
4925
  }) })
4399
4926
  ] }),
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
- )
4927
+ /* @__PURE__ */ jsxs7("div", { className: "df-review-list-title", children: [
4928
+ /* @__PURE__ */ jsx9("span", { children: isAllQaVisible ? `${label} QA \xB7 All pages` : `${label} QA` }),
4929
+ /* @__PURE__ */ jsx9("strong", { title: `${activeRemainingItemCount} remaining of ${activeItemCount}`, children: !hasActiveFilter ? `${activeRemainingItemCount}/${activeItemCount}` : `${filteredItemCount}/${activeItemCount}` }),
4930
+ /* @__PURE__ */ jsxs7(
4931
+ "select",
4932
+ {
4933
+ "aria-label": "QA status filter",
4934
+ className: "df-review-status-filter-select",
4935
+ value: qaStatusFilter,
4936
+ onChange: (event) => onQaStatusFilterChange(
4937
+ event.currentTarget.value
4938
+ ),
4939
+ children: [
4940
+ /* @__PURE__ */ jsx9("option", { value: "all", children: `All status (${qaStatusFilterCounts.get("all") ?? 0})` }),
4941
+ statusFilterOptions.map((statusOption) => /* @__PURE__ */ jsx9("option", { value: statusOption.value, children: `${statusOption.label} (${qaStatusFilterCounts.get(statusOption.value) ?? 0})` }, statusOption.value))
4942
+ ]
4943
+ }
4944
+ )
4945
+ ] })
4417
4946
  ] });
4418
4947
  };
4948
+ function getStatusFilterOptions(statusOptions) {
4949
+ const seen = /* @__PURE__ */ new Set();
4950
+ return statusOptions.flatMap((statusOption) => {
4951
+ const value = normalizeReviewItemStatus(statusOption.value);
4952
+ if (seen.has(value)) return [];
4953
+ seen.add(value);
4954
+ return [{
4955
+ value,
4956
+ label: statusOption.label
4957
+ }];
4958
+ });
4959
+ }
4419
4960
 
4420
4961
  // src/react-shell/qa/panel.tsx
4421
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
4962
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
4422
4963
  var ReviewQaPanel = ({
4423
4964
  activeAdapterEntry,
4424
4965
  activeItems,
4425
- currentPagePresenceUsers,
4966
+ activeRemainingItemCount,
4426
4967
  currentPresetScope,
4427
4968
  filteredNumberedActiveItems,
4428
4969
  getItemPresetScope,
4429
4970
  hiddenOverlayItemIds,
4971
+ isAllQaVisible,
4430
4972
  isListVisible,
4431
4973
  isRemoteSource,
4432
- presenceSessionId,
4433
4974
  copiedPromptKey,
4434
4975
  qaFilter,
4435
4976
  qaFilterCounts,
4977
+ qaStatusFilter,
4978
+ qaStatusFilterCounts,
4436
4979
  remoteAdapterEntry,
4437
4980
  selectedItemId,
4438
4981
  showSourceSelect,
4439
- sourceRoot,
4440
4982
  source,
4441
4983
  sourceEntries,
4442
4984
  onChangeItemStatus,
4443
4985
  onClearSelectedItem,
4444
4986
  onChangeReviewSource,
4987
+ onCopyItemLink,
4445
4988
  onCopyItemPrompt,
4446
4989
  onEditItem,
4447
4990
  onQaFilterChange,
4991
+ onQaStatusFilterChange,
4448
4992
  onRefreshReviewData,
4449
4993
  onRemoveItem,
4450
4994
  onRestoreReviewItem,
4451
4995
  onSubmitItem,
4452
4996
  onToggleItemOverlayVisibility
4453
4997
  }) => {
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(
4998
+ const emptyMessage = isAllQaVisible ? `No ${activeAdapterEntry.label} QA.` : isRemoteSource ? `No ${activeAdapterEntry.label} QA on this page.` : "No QA on this page.";
4999
+ 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: [
5000
+ /* @__PURE__ */ jsx10(
4456
5001
  QaPanelHeader,
4457
5002
  {
4458
5003
  activeItemCount: activeItems.length,
4459
- currentPagePresenceUsers,
5004
+ activeRemainingItemCount,
4460
5005
  filteredItemCount: filteredNumberedActiveItems.length,
5006
+ isAllQaVisible,
4461
5007
  label: activeAdapterEntry.label,
4462
- presenceSessionId,
4463
5008
  qaFilter,
4464
5009
  qaFilterCounts,
5010
+ qaStatusFilter,
5011
+ qaStatusFilterCounts,
4465
5012
  showSourceSelect,
4466
5013
  source,
4467
5014
  sourceEntries,
5015
+ statusOptions: activeAdapterEntry.statusOptions,
4468
5016
  onChangeReviewSource,
4469
5017
  onQaFilterChange,
5018
+ onQaStatusFilterChange,
4470
5019
  onRefreshReviewData
4471
5020
  }
4472
5021
  ),
4473
- /* @__PURE__ */ jsxs9(
5022
+ /* @__PURE__ */ jsxs8(
4474
5023
  "div",
4475
5024
  {
4476
5025
  className: "df-review-list-scroll",
@@ -4480,11 +5029,11 @@ var ReviewQaPanel = ({
4480
5029
  }
4481
5030
  },
4482
5031
  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." }),
5032
+ activeItems.length === 0 && /* @__PURE__ */ jsx10("p", { className: "df-review-empty", children: emptyMessage }),
5033
+ activeItems.length > 0 && filteredNumberedActiveItems.length === 0 && /* @__PURE__ */ jsx10("p", { className: "df-review-empty", children: "No QA in this filter." }),
4485
5034
  filteredNumberedActiveItems.map((numberedItem) => {
4486
5035
  const { item } = numberedItem;
4487
- return /* @__PURE__ */ jsx11(
5036
+ return /* @__PURE__ */ jsx10(
4488
5037
  QaItemCard,
4489
5038
  {
4490
5039
  activeAdapterEntry,
@@ -4496,9 +5045,9 @@ var ReviewQaPanel = ({
4496
5045
  remoteAdapterEntry,
4497
5046
  copiedPromptKey,
4498
5047
  selectedItemId,
4499
- sourceRoot,
4500
5048
  onChangeItemStatus,
4501
5049
  onClearSelectedItem,
5050
+ onCopyItemLink,
4502
5051
  onCopyItemPrompt,
4503
5052
  onEditItem,
4504
5053
  onRemoveItem,
@@ -4515,6 +5064,263 @@ var ReviewQaPanel = ({
4515
5064
  ] }) }) });
4516
5065
  };
4517
5066
 
5067
+ // src/react-shell/presence/overlay.tsx
5068
+ import { useState as useState3 } from "react";
5069
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
5070
+ var COLLAPSED_USER_COUNT = 1;
5071
+ var getPresenceName = (user) => user.displayName || user.userId;
5072
+ var PresenceOverlay = ({
5073
+ presenceSessionId,
5074
+ users
5075
+ }) => {
5076
+ const [isExpanded, setIsExpanded] = useState3(false);
5077
+ if (users.length === 0) return null;
5078
+ const visibleUsers = isExpanded ? users : users.slice(0, COLLAPSED_USER_COUNT);
5079
+ const hiddenUserCount = users.length - visibleUsers.length;
5080
+ return /* @__PURE__ */ jsxs9(
5081
+ "div",
5082
+ {
5083
+ "aria-label": `Review presence, ${users.length} online`,
5084
+ className: `df-review-presence-overlay${isExpanded ? " is-expanded" : ""}`,
5085
+ children: [
5086
+ visibleUsers.map((user) => /* @__PURE__ */ jsx11(
5087
+ "span",
5088
+ {
5089
+ className: `df-review-presence-chip${user.sessionId === presenceSessionId ? " is-self" : ""}`,
5090
+ style: {
5091
+ "--df-review-presence-color": user.color
5092
+ },
5093
+ title: getPresenceName(user),
5094
+ children: getPresenceName(user)
5095
+ },
5096
+ user.sessionId
5097
+ )),
5098
+ hiddenUserCount > 0 && /* @__PURE__ */ jsxs9(
5099
+ "button",
5100
+ {
5101
+ "aria-label": `Show ${hiddenUserCount} more online reviewers`,
5102
+ "aria-expanded": isExpanded,
5103
+ className: "df-review-presence-more",
5104
+ type: "button",
5105
+ onClick: () => setIsExpanded(true),
5106
+ children: [
5107
+ "+",
5108
+ hiddenUserCount
5109
+ ]
5110
+ }
5111
+ )
5112
+ ]
5113
+ }
5114
+ );
5115
+ };
5116
+
5117
+ // src/react-shell/source.open.ts
5118
+ var SOURCE_SELECTOR = [
5119
+ "[data-wrk-source-file]",
5120
+ "[data-wrk-source-component]",
5121
+ "[data-wrk-source-line]",
5122
+ "[data-wrk-source-column]",
5123
+ "[data-file]",
5124
+ "[data-component]",
5125
+ "[data-section-index]",
5126
+ "[data-section-id]"
5127
+ ].join(", ");
5128
+ var getSourceCandidates = (target) => {
5129
+ const startElement = getEventElement(target);
5130
+ if (!startElement) return [];
5131
+ const candidates = [];
5132
+ const seen = /* @__PURE__ */ new Set();
5133
+ let element = startElement;
5134
+ let depth = 0;
5135
+ while (element && element.nodeType === 1) {
5136
+ const source = getSourceHintFromElement(element);
5137
+ if (source?.file) {
5138
+ const key = getSourceCandidateKey(source);
5139
+ if (!seen.has(key)) {
5140
+ seen.add(key);
5141
+ candidates.push(createSourceCandidate(element, source, depth));
5142
+ }
5143
+ }
5144
+ if (element === element.ownerDocument.documentElement) break;
5145
+ element = element.parentElement;
5146
+ depth += 1;
5147
+ }
5148
+ return candidates.slice(0, 8);
5149
+ };
5150
+ var getSourceOpenUrl = (source, options) => {
5151
+ const normalizedOptions = normalizeSourceOpenOptions(options);
5152
+ const file = source?.file?.trim();
5153
+ if (!file) return null;
5154
+ const sourcePath = getSourcePath(file, normalizedOptions.sourceRoot);
5155
+ if (!sourcePath) return null;
5156
+ const hasPosition = !normalizedOptions.omitPosition;
5157
+ const line = hasPosition ? getSourcePosition(source?.line) : null;
5158
+ const column = hasPosition ? getSourcePosition(source?.column) : null;
5159
+ const editor = normalizedOptions.editor ?? "vscode";
5160
+ if (normalizedOptions.urlTemplate) {
5161
+ return buildSourceUrlFromTemplate(normalizedOptions.urlTemplate, {
5162
+ column,
5163
+ file,
5164
+ line,
5165
+ sourcePath,
5166
+ sourceRoot: normalizedOptions.sourceRoot
5167
+ });
5168
+ }
5169
+ if (editor === "webstorm") {
5170
+ const params = new URLSearchParams({ file: sourcePath });
5171
+ if (line) params.set("line", String(line));
5172
+ if (column) params.set("column", String(column));
5173
+ return `webstorm://open?${params.toString()}`;
5174
+ }
5175
+ if (editor === "custom") return null;
5176
+ const encodedPath = encodePathForFileScheme(sourcePath);
5177
+ const scheme = editor === "cursor" ? "cursor" : "vscode";
5178
+ if (!line) return `${scheme}://file/${encodedPath}`;
5179
+ if (!column) return `${scheme}://file/${encodedPath}:${line}`;
5180
+ return `${scheme}://file/${encodedPath}:${line}:${column}`;
5181
+ };
5182
+ var openSourceInEditor = (source, options) => {
5183
+ const url = getSourceOpenUrl(source, options);
5184
+ if (!url) return false;
5185
+ window.open(url, "_blank", "noreferrer");
5186
+ return true;
5187
+ };
5188
+ function getSourceHintFromElement(element) {
5189
+ const source = {
5190
+ component: getSourceAttribute(
5191
+ element,
5192
+ "data-wrk-source-component",
5193
+ "data-component"
5194
+ ),
5195
+ file: getSourceAttribute(element, "data-wrk-source-file", "data-file"),
5196
+ line: getSourceAttribute(element, "data-wrk-source-line"),
5197
+ column: getSourceAttribute(element, "data-wrk-source-column"),
5198
+ sectionId: getSourceAttribute(element, "data-section-id"),
5199
+ sectionIndex: getSourceAttribute(element, "data-section-index")
5200
+ };
5201
+ return Object.values(source).some(Boolean) ? source : void 0;
5202
+ }
5203
+ function createSourceCandidate(element, source, depth) {
5204
+ const confidence = getSourceConfidence(source, depth);
5205
+ const fileName = source.file?.split(/[\\/]/).pop() ?? source.file ?? "source";
5206
+ const component = source.component?.trim();
5207
+ const fallbackComponent = getComponentNameFromSourceFile(source.file);
5208
+ const tag = element.tagName.toLowerCase();
5209
+ const line = getSourcePosition(source.line);
5210
+ const column = getSourcePosition(source.column);
5211
+ const position = line ? `:${line}${column ? `:${column}` : ""}` : "";
5212
+ return {
5213
+ id: getSourceCandidateKey(source),
5214
+ depth,
5215
+ element,
5216
+ filePath: getDisplaySourcePath(source.file) ?? fileName,
5217
+ label: component || fallbackComponent || tag,
5218
+ detail: `${fileName}${position}`,
5219
+ positionLabel: line ? `${line}:${column ?? 1}` : "",
5220
+ confidence,
5221
+ confidenceLabel: confidence >= 0.82 ? "high" : confidence >= 0.58 ? "medium" : "low",
5222
+ usesPosition: confidence >= 0.72 && Boolean(line),
5223
+ source
5224
+ };
5225
+ }
5226
+ function getComponentNameFromSourceFile(file) {
5227
+ const normalizedFile = file?.trim().replace(/\\/g, "/");
5228
+ if (!normalizedFile) return void 0;
5229
+ const pathParts = normalizedFile.split("/").filter(Boolean);
5230
+ const fileName = pathParts[pathParts.length - 1];
5231
+ if (!fileName) return void 0;
5232
+ const stem = fileName.replace(/\.[^.]+$/, "");
5233
+ const sourceName = stem.toLowerCase() === "index" ? pathParts[pathParts.length - 2]?.replace(/\.[^.]+$/, "") : stem;
5234
+ return toPascalCase2(sourceName);
5235
+ }
5236
+ function toPascalCase2(value) {
5237
+ const words = value?.split(/[._\-\s]+/).map((part) => part.trim()).filter(Boolean);
5238
+ if (!words?.length) return void 0;
5239
+ return words.map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join("");
5240
+ }
5241
+ function getDisplaySourcePath(file) {
5242
+ const normalizedFile = file?.trim().replace(/\\/g, "/");
5243
+ if (!normalizedFile) return void 0;
5244
+ const sourceRootMatch = normalizedFile.match(
5245
+ /(?:^|\/)((?:dev\/)?src\/.+|app\/.+|pages\/.+|components\/.+)$/
5246
+ );
5247
+ return sourceRootMatch?.[1] ?? normalizedFile;
5248
+ }
5249
+ function getSourceCandidateKey(source) {
5250
+ return [
5251
+ source.file,
5252
+ source.component,
5253
+ source.line,
5254
+ source.column,
5255
+ source.sectionId,
5256
+ source.sectionIndex
5257
+ ].filter(Boolean).join("|");
5258
+ }
5259
+ function getSourceConfidence(source, depth) {
5260
+ let score = source.file ? 0.54 : 0.12;
5261
+ if (source.component) score += 0.12;
5262
+ if (getSourcePosition(source.line)) score += 0.22;
5263
+ if (getSourcePosition(source.column)) score += 0.08;
5264
+ if (source.sectionId || source.sectionIndex) score += 0.04;
5265
+ score -= Math.min(depth, 5) * 0.045;
5266
+ return Math.max(0.1, Math.min(1, Number(score.toFixed(2))));
5267
+ }
5268
+ function normalizeSourceOpenOptions(options) {
5269
+ return typeof options === "string" ? { sourceRoot: options } : options ?? {};
5270
+ }
5271
+ function buildSourceUrlFromTemplate(template, values) {
5272
+ const replacements = {
5273
+ column: values.column ? String(values.column) : "",
5274
+ encodedPath: encodeURIComponent(values.sourcePath),
5275
+ file: values.file,
5276
+ line: values.line ? String(values.line) : "",
5277
+ path: values.sourcePath,
5278
+ sourceRoot: values.sourceRoot ?? "",
5279
+ uriPath: encodePathForFileScheme(values.sourcePath)
5280
+ };
5281
+ return template.replace(
5282
+ /\{([a-zA-Z]+)\}/g,
5283
+ (_, key) => replacements[key] ?? ""
5284
+ );
5285
+ }
5286
+ function getSourceAttribute(element, ...names) {
5287
+ for (const name of names) {
5288
+ const value = element.getAttribute(name)?.trim();
5289
+ if (value) return value;
5290
+ }
5291
+ return void 0;
5292
+ }
5293
+ function getEventElement(target) {
5294
+ if (!target || typeof target !== "object") return null;
5295
+ const node = target;
5296
+ if (node.nodeType === 1 && typeof node.closest === "function") {
5297
+ return node;
5298
+ }
5299
+ if (node.parentElement && typeof node.parentElement.closest === "function") {
5300
+ return node.parentElement;
5301
+ }
5302
+ return null;
5303
+ }
5304
+ function getSourcePath(file, sourceRoot) {
5305
+ const normalizedFile = file.replace(/\\/g, "/");
5306
+ if (normalizedFile.startsWith("/") || /^[a-zA-Z]:\//.test(normalizedFile)) {
5307
+ return normalizedFile;
5308
+ }
5309
+ const normalizedRoot = sourceRoot?.trim().replace(/\\/g, "/").replace(/\/+$/, "");
5310
+ if (!normalizedRoot) return null;
5311
+ return `${normalizedRoot}/${normalizedFile.replace(/^\/+/, "")}`;
5312
+ }
5313
+ function getSourcePosition(value) {
5314
+ const position = Number(value);
5315
+ return Number.isInteger(position) && position > 0 ? position : null;
5316
+ }
5317
+ function encodePathForFileScheme(path) {
5318
+ return encodeURI(path).replace(
5319
+ /[#?]/g,
5320
+ (match) => match === "#" ? "%23" : "%3F"
5321
+ );
5322
+ }
5323
+
4518
5324
  // src/react-shell/review/mode.toolbar.tsx
4519
5325
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
4520
5326
  var ReviewModeToolbar = ({
@@ -4693,6 +5499,7 @@ var ReviewTargetFrame = ({
4693
5499
  canWriteArea,
4694
5500
  canWriteDom,
4695
5501
  frameScrollRef,
5502
+ figmaFrameUrl,
4696
5503
  iframeRef,
4697
5504
  isRulerAvailable,
4698
5505
  isRulerDragging,
@@ -4711,8 +5518,9 @@ var ReviewTargetFrame = ({
4711
5518
  onSetReviewMode
4712
5519
  }) => {
4713
5520
  const showRuler = isRulerVisible && isRulerAvailable;
5521
+ const targetHref = getTargetOpenHref(targetSrc);
4714
5522
  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(
5523
+ /* @__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
5524
  "div",
4717
5525
  {
4718
5526
  className: `df-review-device-frame${showRuler ? " is-ruler" : ""}`,
@@ -4764,10 +5572,36 @@ var ReviewTargetFrame = ({
4764
5572
  )
4765
5573
  ]
4766
5574
  }
4767
- )
5575
+ ),
5576
+ /* @__PURE__ */ jsxs13("div", { className: "df-review-frame-link-stack", children: [
5577
+ /* @__PURE__ */ jsx15(
5578
+ "a",
5579
+ {
5580
+ "aria-label": "Open target page",
5581
+ className: "df-review-frame-link is-target",
5582
+ href: targetHref,
5583
+ rel: "noreferrer",
5584
+ target: "_blank",
5585
+ title: "Open target page",
5586
+ children: /* @__PURE__ */ jsx15(ExternalLink, { "aria-hidden": "true" })
5587
+ }
5588
+ ),
5589
+ figmaFrameUrl && /* @__PURE__ */ jsx15(
5590
+ "a",
5591
+ {
5592
+ "aria-label": "Open Figma frame",
5593
+ className: "df-review-frame-link is-figma",
5594
+ href: figmaFrameUrl,
5595
+ rel: "noreferrer",
5596
+ target: "_blank",
5597
+ title: "Open Figma frame",
5598
+ children: /* @__PURE__ */ jsx15(FigmaIcon, {})
5599
+ }
5600
+ )
5601
+ ] })
4768
5602
  ]
4769
5603
  }
4770
- ) }) }),
5604
+ ) }) }) }),
4771
5605
  /* @__PURE__ */ jsx15("div", { className: "df-review-frame-actions", children: /* @__PURE__ */ jsx15(
4772
5606
  ReviewModeToolbar,
4773
5607
  {
@@ -4779,6 +5613,111 @@ var ReviewTargetFrame = ({
4779
5613
  ) })
4780
5614
  ] }) });
4781
5615
  };
5616
+ function getTargetOpenHref(targetSrc) {
5617
+ const url = new URL(targetSrc, window.location.origin);
5618
+ url.searchParams.delete("__dfwr_target");
5619
+ return `${url.pathname}${url.search}${url.hash}`;
5620
+ }
5621
+ var FigmaIcon = () => /* @__PURE__ */ jsx15(
5622
+ "svg",
5623
+ {
5624
+ "aria-hidden": "true",
5625
+ viewBox: "0 0 24 24",
5626
+ xmlns: "http://www.w3.org/2000/svg",
5627
+ 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" })
5628
+ }
5629
+ );
5630
+
5631
+ // src/react-shell/target/target.ts
5632
+ var HIDE_SCROLLBAR_STYLE_ID = "df-review-hide-scrollbar";
5633
+ var FIGMA_POINTER_LOCK_STYLE_ID = "df-review-figma-pointer-lock";
5634
+ var setTargetScrollbarHidden = (targetDocument, hidden) => {
5635
+ if (!targetDocument) return;
5636
+ const existing = targetDocument.getElementById(HIDE_SCROLLBAR_STYLE_ID);
5637
+ if (hidden) {
5638
+ if (existing) return;
5639
+ const style = targetDocument.createElement("style");
5640
+ style.id = HIDE_SCROLLBAR_STYLE_ID;
5641
+ style.textContent = "html{scrollbar-width:none}html::-webkit-scrollbar,body::-webkit-scrollbar{width:0;height:0;display:none}";
5642
+ targetDocument.head?.appendChild(style);
5643
+ } else {
5644
+ existing?.remove();
5645
+ }
5646
+ };
5647
+ var setTargetFigmaOverlayLocked = (targetDocument, locked) => {
5648
+ if (!targetDocument) return;
5649
+ const existing = targetDocument.getElementById(FIGMA_POINTER_LOCK_STYLE_ID);
5650
+ if (locked) {
5651
+ if (existing) return;
5652
+ const style = targetDocument.createElement("style");
5653
+ style.id = FIGMA_POINTER_LOCK_STYLE_ID;
5654
+ style.textContent = [
5655
+ ".helper-figma-root,",
5656
+ ".helper-figma-root *,",
5657
+ ".helper-figma-loading-backdrop,",
5658
+ ".helper-figma-loading-backdrop * {",
5659
+ "pointer-events: none !important;",
5660
+ "}"
5661
+ ].join("\n");
5662
+ targetDocument.head?.appendChild(style);
5663
+ } else {
5664
+ existing?.remove();
5665
+ }
5666
+ };
5667
+ var isEditableEventTarget = (event) => {
5668
+ const path = event.composedPath?.() ?? [];
5669
+ const element = path[0] ?? event.target;
5670
+ if (!element || typeof element.tagName !== "string") return false;
5671
+ const tag = element.tagName;
5672
+ return tag === "INPUT" || tag === "TEXTAREA" || element.isContentEditable === true;
5673
+ };
5674
+ var TRUE_STORAGE_VALUES = /* @__PURE__ */ new Set([
5675
+ "1",
5676
+ "true",
5677
+ "on",
5678
+ "show",
5679
+ "shown",
5680
+ "visible",
5681
+ "enabled",
5682
+ "yes"
5683
+ ]);
5684
+ var OVERLAY_STORAGE_KEYS = {
5685
+ grid: ["isHelp", "df-review-grid-overlay", "dfReviewGridOverlay"],
5686
+ figma: ["isFigmaHelp", "df-review-figma-overlay", "dfReviewFigmaOverlay"]
5687
+ };
5688
+ var isStoredOverlayEnabled = (value) => TRUE_STORAGE_VALUES.has(value?.trim().toLowerCase() ?? "");
5689
+ var getCookieValue = (targetDocument, name) => {
5690
+ const cookies = targetDocument?.cookie ? targetDocument.cookie.split(";") : [];
5691
+ const prefix = `${name}=`;
5692
+ const match = cookies.map((cookie) => cookie.trim()).find((cookie) => cookie.startsWith(prefix));
5693
+ return match ? decodeURIComponent(match.slice(prefix.length)) : null;
5694
+ };
5695
+ var getStorageValue = (storage, key) => {
5696
+ try {
5697
+ return storage?.getItem(key) ?? null;
5698
+ } catch {
5699
+ return null;
5700
+ }
5701
+ };
5702
+ var getStoredOverlayState = (targetDocument, overlay) => {
5703
+ const targetWindow = targetDocument?.defaultView;
5704
+ return OVERLAY_STORAGE_KEYS[overlay].some((key) => {
5705
+ if (isStoredOverlayEnabled(getCookieValue(targetDocument, key))) {
5706
+ return true;
5707
+ }
5708
+ return isStoredOverlayEnabled(getStorageValue(targetWindow?.localStorage, key)) || isStoredOverlayEnabled(getStorageValue(targetWindow?.sessionStorage, key));
5709
+ });
5710
+ };
5711
+ var getTargetOverlayState = (targetDocument) => ({
5712
+ grid: Boolean(
5713
+ targetDocument?.body.classList.contains("is-help") || targetDocument?.querySelector(".helper.onShow") || getStoredOverlayState(targetDocument, "grid")
5714
+ ),
5715
+ figma: Boolean(
5716
+ targetDocument?.querySelector(
5717
+ ".helper-figma-root, .helper-figma-loading-backdrop"
5718
+ ) || getStoredOverlayState(targetDocument, "figma")
5719
+ )
5720
+ });
4782
5721
 
4783
5722
  // src/react-shell/topbar.tsx
4784
5723
  import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
@@ -4897,6 +5836,7 @@ var ReviewTopbar = ({
4897
5836
  "aria-disabled": !isFigmaOverlayAvailable,
4898
5837
  "aria-label": isFigmaOverlayAvailable ? "Toggle Figma overlay" : FIGMA_OVERLAY_UNAVAILABLE_MESSAGE,
4899
5838
  className: `df-review-overlay-button is-figma${targetOverlayState.figma ? " is-active" : ""}${isFigmaOverlayAvailable ? "" : " is-disabled"}`,
5839
+ disabled: !isFigmaOverlayAvailable,
4900
5840
  type: "button",
4901
5841
  onClick: () => onToggleTargetOverlay("figma"),
4902
5842
  children: /* @__PURE__ */ jsx16(Image, { "aria-hidden": "true" })
@@ -4954,6 +5894,47 @@ function runWithAutoScrollBehavior(targetDocument, callback) {
4954
5894
  });
4955
5895
  }
4956
5896
  }
5897
+ var RESTORE_WAIT_MAX_MS = 2600;
5898
+ var RESTORE_STABLE_FRAME_COUNT = 2;
5899
+ var waitForNextAnimationFrame = (targetWindow) => new Promise((resolve) => {
5900
+ targetWindow.requestAnimationFrame(() => resolve());
5901
+ });
5902
+ var getRestoreLayoutSnapshot = (targetDocument, anchorElement) => {
5903
+ const root = targetDocument.documentElement;
5904
+ const body = targetDocument.body;
5905
+ const anchorRect = anchorElement?.getBoundingClientRect();
5906
+ return [
5907
+ root.scrollWidth,
5908
+ root.scrollHeight,
5909
+ body?.scrollWidth ?? 0,
5910
+ body?.scrollHeight ?? 0,
5911
+ anchorRect ? Math.round(anchorRect.left) : -1,
5912
+ anchorRect ? Math.round(anchorRect.top) : -1,
5913
+ anchorRect ? Math.round(anchorRect.width) : -1,
5914
+ anchorRect ? Math.round(anchorRect.height) : -1
5915
+ ].join(":");
5916
+ };
5917
+ var waitForRestoreAnchor = async (targetWindow, targetDocument, item, isCurrent) => {
5918
+ const startedAt = targetWindow.performance.now();
5919
+ let previousSnapshot = "";
5920
+ let stableFrameCount = 0;
5921
+ while (isCurrent() && targetWindow.performance.now() - startedAt < RESTORE_WAIT_MAX_MS) {
5922
+ const anchorElement = queryReviewItemAnchorElement(targetDocument, item);
5923
+ const snapshot = getRestoreLayoutSnapshot(targetDocument, anchorElement);
5924
+ const canRestore = item.anchor ? Boolean(anchorElement) : true;
5925
+ if (snapshot === previousSnapshot) {
5926
+ stableFrameCount += 1;
5927
+ } else {
5928
+ stableFrameCount = 0;
5929
+ }
5930
+ if (canRestore && stableFrameCount >= RESTORE_STABLE_FRAME_COUNT) {
5931
+ return anchorElement;
5932
+ }
5933
+ previousSnapshot = snapshot;
5934
+ await waitForNextAnimationFrame(targetWindow);
5935
+ }
5936
+ return queryReviewItemAnchorElement(targetDocument, item);
5937
+ };
4957
5938
  var useReviewItemRestore = ({
4958
5939
  adapter,
4959
5940
  controllerRef,
@@ -4984,14 +5965,20 @@ var useReviewItemRestore = ({
4984
5965
  selectedItemIdRef
4985
5966
  ]);
4986
5967
  const applyItemScroll = useCallback(
4987
- (item) => {
4988
- if (selectedItemIdRef.current !== item.id) return;
5968
+ async (item) => {
5969
+ if (selectedItemIdRef.current !== item.id) return false;
4989
5970
  const targetWindow = iframeRef.current?.contentWindow;
4990
5971
  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;
5972
+ if (!targetWindow || !targetDocument) return false;
5973
+ const isCurrentRestore = () => selectedItemIdRef.current === item.id && iframeRef.current?.contentDocument === targetDocument;
5974
+ const anchorElement = await waitForRestoreAnchor(
5975
+ targetWindow,
5976
+ targetDocument,
5977
+ item,
5978
+ isCurrentRestore
5979
+ );
5980
+ if (!isCurrentRestore()) return false;
5981
+ runWithAutoScrollBehavior(targetDocument, () => {
4995
5982
  setDocumentScrollInstantly(
4996
5983
  targetWindow,
4997
5984
  targetDocument,
@@ -5005,14 +5992,18 @@ var useReviewItemRestore = ({
5005
5992
  });
5006
5993
  onSyncTargetViewport();
5007
5994
  controllerRef.current?.highlightItem(item.id);
5995
+ return true;
5008
5996
  },
5009
5997
  [controllerRef, iframeRef, onSyncTargetViewport, selectedItemIdRef]
5010
5998
  );
5011
5999
  const applyPendingRestore = useCallback(() => {
5012
6000
  const item = pendingRestoreRef.current;
5013
6001
  if (!item) return;
5014
- applyItemScroll(item);
5015
- pendingRestoreRef.current = null;
6002
+ void applyItemScroll(item).then((didApply) => {
6003
+ if (didApply && pendingRestoreRef.current?.id === item.id) {
6004
+ pendingRestoreRef.current = null;
6005
+ }
6006
+ });
5016
6007
  }, [applyItemScroll, pendingRestoreRef]);
5017
6008
  const restoreReviewItem = useCallback(
5018
6009
  (item) => {
@@ -5069,39 +6060,6 @@ import {
5069
6060
  useEffect as useEffect2
5070
6061
  } from "react";
5071
6062
 
5072
- // src/react-shell/target/target.ts
5073
- var HIDE_SCROLLBAR_STYLE_ID = "df-review-hide-scrollbar";
5074
- var setTargetScrollbarHidden = (targetDocument, hidden) => {
5075
- if (!targetDocument) return;
5076
- const existing = targetDocument.getElementById(HIDE_SCROLLBAR_STYLE_ID);
5077
- if (hidden) {
5078
- if (existing) return;
5079
- const style = targetDocument.createElement("style");
5080
- style.id = HIDE_SCROLLBAR_STYLE_ID;
5081
- style.textContent = "html{scrollbar-width:none}html::-webkit-scrollbar,body::-webkit-scrollbar{width:0;height:0;display:none}";
5082
- targetDocument.head?.appendChild(style);
5083
- } else {
5084
- existing?.remove();
5085
- }
5086
- };
5087
- var isEditableEventTarget = (event) => {
5088
- const path = event.composedPath?.() ?? [];
5089
- const element = path[0] ?? event.target;
5090
- if (!element || typeof element.tagName !== "string") return false;
5091
- const tag = element.tagName;
5092
- return tag === "INPUT" || tag === "TEXTAREA" || element.isContentEditable === true;
5093
- };
5094
- var getTargetOverlayState = (targetDocument) => ({
5095
- grid: Boolean(
5096
- targetDocument?.body.classList.contains("is-help") || targetDocument?.querySelector(".helper.onShow")
5097
- ),
5098
- figma: Boolean(
5099
- targetDocument?.querySelector(
5100
- ".helper-figma-root, .helper-figma-loading-backdrop"
5101
- )
5102
- )
5103
- });
5104
-
5105
6063
  // src/react-shell/hooks/review.frame.navigation.ts
5106
6064
  var bindReviewFrameNavigation = ({
5107
6065
  pageTargets,
@@ -5227,6 +6185,7 @@ var useReviewKitLifecycle = ({
5227
6185
  reviewUserId,
5228
6186
  reviewViewportPresets,
5229
6187
  ruler,
6188
+ adjustmentLabel,
5230
6189
  sizeRef,
5231
6190
  targetRef,
5232
6191
  onApplyPendingRestore,
@@ -5279,6 +6238,7 @@ var useReviewKitLifecycle = ({
5279
6238
  presets: reviewViewportPresets
5280
6239
  },
5281
6240
  ruler,
6241
+ adjustmentLabel,
5282
6242
  onCreateItem,
5283
6243
  onRestoreItem: onRestoreReviewItem,
5284
6244
  onItemsChange: () => {
@@ -5298,8 +6258,7 @@ var useReviewKitLifecycle = ({
5298
6258
  controllerRef.current.setHiddenItemIds(hiddenOverlayItemIdListRef.current);
5299
6259
  onModeChange(controllerRef.current.getMode());
5300
6260
  void onItemsRefresh();
5301
- void onRestoreInitialItem();
5302
- onApplyPendingRestore();
6261
+ void onRestoreInitialItem().then(onApplyPendingRestore);
5303
6262
  onRefreshTargetOverlayState();
5304
6263
  setTargetScrollbarHidden(
5305
6264
  targetDocument,
@@ -5330,6 +6289,7 @@ var useReviewKitLifecycle = ({
5330
6289
  reviewUserId,
5331
6290
  reviewViewportPresets,
5332
6291
  ruler,
6292
+ adjustmentLabel,
5333
6293
  sizeRef,
5334
6294
  targetRef
5335
6295
  ]);
@@ -5362,18 +6322,33 @@ var useReviewKitLifecycle = ({
5362
6322
  };
5363
6323
 
5364
6324
  // src/react-shell/hooks/use.review.target.overlay.ts
5365
- import { useCallback as useCallback3, useEffect as useEffect3 } from "react";
6325
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef } from "react";
6326
+ var TARGET_OVERLAY_REFRESH_DELAYS = [80, 240, 600];
5366
6327
  var useReviewTargetOverlay = ({
5367
6328
  iframeRef,
5368
6329
  isFigmaOverlayAvailable,
5369
6330
  targetOverlayState,
5370
6331
  onTargetOverlayStateChange
5371
6332
  }) => {
5372
- const refreshTargetOverlayState = useCallback3(() => {
5373
- onTargetOverlayStateChange(
5374
- getTargetOverlayState(iframeRef.current?.contentDocument ?? void 0)
6333
+ const refreshTimersRef = useRef([]);
6334
+ const clearRefreshTimers = useCallback3(() => {
6335
+ refreshTimersRef.current.forEach((timer) => window.clearTimeout(timer));
6336
+ refreshTimersRef.current = [];
6337
+ }, []);
6338
+ const updateTargetOverlayState = useCallback3(() => {
6339
+ const state = getTargetOverlayState(
6340
+ iframeRef.current?.contentDocument ?? void 0
5375
6341
  );
6342
+ onTargetOverlayStateChange(state);
6343
+ return state;
5376
6344
  }, [iframeRef, onTargetOverlayStateChange]);
6345
+ const refreshTargetOverlayState = useCallback3(() => {
6346
+ clearRefreshTimers();
6347
+ updateTargetOverlayState();
6348
+ refreshTimersRef.current = TARGET_OVERLAY_REFRESH_DELAYS.map(
6349
+ (delay) => window.setTimeout(updateTargetOverlayState, delay)
6350
+ );
6351
+ }, [clearRefreshTimers, updateTargetOverlayState]);
5377
6352
  const dispatchTargetOverlayHotkey = useCallback3(
5378
6353
  (overlay) => {
5379
6354
  const targetWindow = iframeRef.current?.contentWindow;
@@ -5409,19 +6384,17 @@ var useReviewTargetOverlay = ({
5409
6384
  );
5410
6385
  const closeTargetOverlay = useCallback3(
5411
6386
  (overlay) => {
5412
- const currentState = getTargetOverlayState(
5413
- iframeRef.current?.contentDocument ?? void 0
5414
- );
5415
- onTargetOverlayStateChange(currentState);
6387
+ const currentState = updateTargetOverlayState();
5416
6388
  if (!currentState[overlay]) return false;
5417
6389
  return dispatchTargetOverlayHotkey(overlay);
5418
6390
  },
5419
- [dispatchTargetOverlayHotkey, iframeRef, onTargetOverlayStateChange]
6391
+ [dispatchTargetOverlayHotkey, updateTargetOverlayState]
5420
6392
  );
5421
6393
  useEffect3(() => {
5422
6394
  if (isFigmaOverlayAvailable || !targetOverlayState.figma) return;
5423
6395
  closeTargetOverlay("figma");
5424
6396
  }, [closeTargetOverlay, isFigmaOverlayAvailable, targetOverlayState.figma]);
6397
+ useEffect3(() => clearRefreshTimers, [clearRefreshTimers]);
5425
6398
  return {
5426
6399
  closeTargetOverlay,
5427
6400
  refreshTargetOverlayState,
@@ -5535,6 +6508,7 @@ var useReviewController = ({
5535
6508
  reviewUserId,
5536
6509
  reviewViewportPresets,
5537
6510
  ruler,
6511
+ adjustmentLabel,
5538
6512
  selectedItemIdRef,
5539
6513
  size,
5540
6514
  sizeRef,
@@ -5623,6 +6597,7 @@ var useReviewController = ({
5623
6597
  reviewUserId,
5624
6598
  reviewViewportPresets,
5625
6599
  ruler,
6600
+ adjustmentLabel,
5626
6601
  sizeRef,
5627
6602
  targetRef,
5628
6603
  onApplyPendingRestore: applyPendingRestore,
@@ -5653,9 +6628,9 @@ var useReviewController = ({
5653
6628
  import {
5654
6629
  useCallback as useCallback6,
5655
6630
  useEffect as useEffect5,
5656
- useMemo as useMemo2,
5657
- useRef,
5658
- useState as useState2
6631
+ useMemo as useMemo3,
6632
+ useRef as useRef2,
6633
+ useState as useState4
5659
6634
  } from "react";
5660
6635
 
5661
6636
  // src/react-shell/presence/presence.ts
@@ -5871,10 +6846,10 @@ var useReviewPresence = ({
5871
6846
  size,
5872
6847
  source
5873
6848
  }) => {
5874
- const presenceSessionRef = useRef(null);
5875
- const [presenceUsers, setPresenceUsers] = useState2([]);
5876
- const [presenceSessionVersion, setPresenceSessionVersion] = useState2(0);
5877
- const presenceSessionId = useMemo2(getReviewPresenceSessionId, []);
6849
+ const presenceSessionRef = useRef2(null);
6850
+ const [presenceUsers, setPresenceUsers] = useState4([]);
6851
+ const [presenceSessionVersion, setPresenceSessionVersion] = useState4(0);
6852
+ const presenceSessionId = useMemo3(getReviewPresenceSessionId, []);
5878
6853
  const normalizedReviewUserId = reviewUserId.trim();
5879
6854
  const presenceDisplayName = getReviewPresenceDisplayName(
5880
6855
  normalizedReviewUserId
@@ -5882,7 +6857,7 @@ var useReviewPresence = ({
5882
6857
  const presenceColor = getReviewPresenceColor(
5883
6858
  normalizedReviewUserId || presenceSessionId
5884
6859
  );
5885
- const presenceViewport = useMemo2(
6860
+ const presenceViewport = useMemo3(
5886
6861
  () => ({
5887
6862
  label: size.label,
5888
6863
  width: size.width,
@@ -5892,7 +6867,7 @@ var useReviewPresence = ({
5892
6867
  [size]
5893
6868
  );
5894
6869
  const presenceStatus = mode === "idle" ? "reviewing" : "editing";
5895
- const visiblePresenceUsers = useMemo2(
6870
+ const visiblePresenceUsers = useMemo3(
5896
6871
  () => {
5897
6872
  const projectPresenceUsers = presenceUsers.filter(
5898
6873
  (user) => user.projectId === projectId && user.userId.trim()
@@ -5904,14 +6879,14 @@ var useReviewPresence = ({
5904
6879
  },
5905
6880
  [presenceUsers, projectId, reviewPathPrefix]
5906
6881
  );
5907
- const currentPagePresenceUsers = useMemo2(
6882
+ const currentPagePresenceUsers = useMemo3(
5908
6883
  () => visiblePresenceUsers.filter((user) => {
5909
6884
  const userTarget = getPresenceUserTarget(user, reviewPathPrefix);
5910
6885
  return userTarget === activeRoute;
5911
6886
  }),
5912
6887
  [activeRoute, reviewPathPrefix, visiblePresenceUsers]
5913
6888
  );
5914
- const pagePresenceUsers = useMemo2(() => {
6889
+ const pagePresenceUsers = useMemo3(() => {
5915
6890
  const usersByTarget = /* @__PURE__ */ new Map();
5916
6891
  visiblePresenceUsers.forEach((user) => {
5917
6892
  const userTarget = getPresenceUserTarget(user, reviewPathPrefix);
@@ -5952,7 +6927,7 @@ var useReviewPresence = ({
5952
6927
  source
5953
6928
  ]
5954
6929
  );
5955
- const getCurrentPresenceStateRef = useRef(getCurrentPresenceState);
6930
+ const getCurrentPresenceStateRef = useRef2(getCurrentPresenceState);
5956
6931
  getCurrentPresenceStateRef.current = getCurrentPresenceState;
5957
6932
  useEffect5(() => {
5958
6933
  if (!presence || !normalizedReviewUserId) {
@@ -6024,16 +6999,16 @@ var useReviewPresence = ({
6024
6999
  import {
6025
7000
  useCallback as useCallback8,
6026
7001
  useEffect as useEffect7,
6027
- useState as useState4
7002
+ useState as useState6
6028
7003
  } from "react";
6029
7004
 
6030
7005
  // src/react-shell/hooks/use.review.ruler.drag.ts
6031
7006
  import {
6032
7007
  useCallback as useCallback7,
6033
7008
  useEffect as useEffect6,
6034
- useMemo as useMemo3,
6035
- useRef as useRef2,
6036
- useState as useState3
7009
+ useMemo as useMemo4,
7010
+ useRef as useRef3,
7011
+ useState as useState5
6037
7012
  } from "react";
6038
7013
 
6039
7014
  // src/react-shell/ruler/ruler.ts
@@ -6063,15 +7038,15 @@ var useReviewRulerDrag = ({
6063
7038
  size,
6064
7039
  targetSrc
6065
7040
  }) => {
6066
- const rulerOverlayRef = useRef2(null);
6067
- const rulerDragRectRef = useRef2(null);
6068
- const isRulerDraggingRef = useRef2(false);
6069
- 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(
7041
+ const rulerOverlayRef = useRef3(null);
7042
+ const rulerDragRectRef = useRef3(null);
7043
+ const isRulerDraggingRef = useRef3(false);
7044
+ const sizeRef = useRef3(size);
7045
+ const [rulerStart, setRulerStart] = useState5(null);
7046
+ const [rulerPoint, setRulerPoint] = useState5(null);
7047
+ const [rulerHover, setRulerHover] = useState5(null);
7048
+ const [isRulerDragging, setIsRulerDragging] = useState5(false);
7049
+ const rulerMeasure = useMemo4(
6075
7050
  () => getRulerMeasure(rulerStart, rulerPoint),
6076
7051
  [rulerPoint, rulerStart]
6077
7052
  );
@@ -6240,7 +7215,7 @@ var useReviewRuler = ({
6240
7215
  onCancelReviewMode,
6241
7216
  onCloseTransientPanels
6242
7217
  }) => {
6243
- const [isRulerVisible, setIsRulerVisible] = useState4(false);
7218
+ const [isRulerVisible, setIsRulerVisible] = useState6(false);
6244
7219
  const isRulerAvailable = ruler?.enabled !== false && typeof size.designWidth === "number" && size.designWidth > 0;
6245
7220
  const rulerUnit = ruler?.unit ?? "px";
6246
7221
  const rulerScaleX = isRulerAvailable && size.designWidth ? size.width / size.designWidth : 1;
@@ -6300,25 +7275,25 @@ var useReviewRuler = ({
6300
7275
  };
6301
7276
 
6302
7277
  // src/react-shell/hooks/use.review.settings.ts
6303
- import { useCallback as useCallback9, useEffect as useEffect8, useState as useState5 } from "react";
7278
+ import { useCallback as useCallback9, useEffect as useEffect8, useState as useState7 } from "react";
6304
7279
  var useReviewSettings = ({
6305
7280
  onCancelReviewMode,
6306
7281
  onCloseInitialPrompt,
6307
7282
  onCloseSitemap,
6308
7283
  onReloadTargetFrame
6309
7284
  }) => {
6310
- const [figmaTokenDraft, setFigmaTokenDraft] = useState5(getStoredFigmaToken);
6311
- const [reviewUserId, setReviewUserId] = useState5(getStoredReviewUserId);
6312
- const [reviewUserIdDraft, setReviewUserIdDraft] = useState5(
7285
+ const [figmaTokenDraft, setFigmaTokenDraft] = useState7(getStoredFigmaToken);
7286
+ const [reviewUserId, setReviewUserId] = useState7(getStoredReviewUserId);
7287
+ const [reviewUserIdDraft, setReviewUserIdDraft] = useState7(
6313
7288
  getStoredReviewUserId
6314
7289
  );
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);
7290
+ const [reviewTheme, setReviewTheme] = useState7(getStoredReviewTheme);
7291
+ const [reviewThemeDraft, setReviewThemeDraft] = useState7(getStoredReviewTheme);
7292
+ const [systemReviewTheme, setSystemReviewTheme] = useState7(getSystemReviewTheme);
7293
+ const [figmaSettingsStatus, setFigmaSettingsStatus] = useState7("");
7294
+ const [isFigmaSettingsOpen, setIsFigmaSettingsOpen] = useState7(false);
7295
+ const [isFigmaTokenVisible, setIsFigmaTokenVisible] = useState7(false);
7296
+ const [isFigmaTokenGuideOpen, setIsFigmaTokenGuideOpen] = useState7(false);
6322
7297
  const effectiveReviewTheme = reviewTheme === "system" ? systemReviewTheme : reviewTheme;
6323
7298
  const closeFigmaSettings = useCallback9(() => {
6324
7299
  setIsFigmaSettingsOpen(false);
@@ -6420,13 +7395,12 @@ var useReviewSettings = ({
6420
7395
  };
6421
7396
 
6422
7397
  // 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
- });
7398
+ import { useCallback as useCallback10, useMemo as useMemo5, useState as useState8 } from "react";
7399
+ var SITEMAP_STATUS_DONE = "done";
6428
7400
  var useReviewShellData = ({
6429
7401
  activeRoute,
7402
+ isAllQaVisible,
7403
+ isRemoteSource,
6430
7404
  pages,
6431
7405
  reviewPathPrefix,
6432
7406
  reviewViewportPresets,
@@ -6435,59 +7409,112 @@ var useReviewShellData = ({
6435
7409
  target,
6436
7410
  viewportPresets
6437
7411
  }) => {
6438
- const [items, setItems] = useState6([]);
6439
- const [hiddenOverlayItemIds, setHiddenOverlayItemIds] = useState6(
7412
+ const [items, setItems] = useState8([]);
7413
+ const [hiddenOverlayItemIds, setHiddenOverlayItemIds] = useState8(
6440
7414
  () => /* @__PURE__ */ new Set()
6441
7415
  );
6442
- const [qaFilter, setQaFilter] = useState6("all");
6443
- const [sitemapItems, setSitemapItems] = useState6(() => ({
7416
+ const [qaFilter, setQaFilter] = useState8("all");
7417
+ const [qaStatusFilter, setQaStatusFilter] = useState8("all");
7418
+ const [sitemapItems, setSitemapItems] = useState8(() => ({
6444
7419
  local: [],
6445
7420
  remote: []
6446
7421
  }));
6447
- const targetSrc = useMemo4(() => buildTargetSrc(target), [target]);
6448
- const pageTargets = useMemo4(
7422
+ const targetSrc = useMemo5(() => buildTargetSrc(target), [target]);
7423
+ const pageTargets = useMemo5(
6449
7424
  () => new Set(
6450
7425
  pages.map((page) => normalizeTarget(page.href, reviewPathPrefix))
6451
7426
  ),
6452
7427
  [pages, reviewPathPrefix]
6453
7428
  );
6454
- const activeItems = useMemo4(
6455
- () => items.filter((item) => getItemTarget(item, reviewPathPrefix) === activeRoute).sort((a, b) => b.createdAt.localeCompare(a.createdAt)),
6456
- [activeRoute, items, reviewPathPrefix]
7429
+ const sitemapSourceItems = useMemo5(
7430
+ () => isRemoteSource ? sitemapItems.remote : sitemapItems.local,
7431
+ [isRemoteSource, sitemapItems]
7432
+ );
7433
+ const activeItems = useMemo5(
7434
+ () => {
7435
+ const sourceItems = isAllQaVisible ? sitemapSourceItems : items.filter(
7436
+ (item) => getItemTarget(item, reviewPathPrefix) === activeRoute
7437
+ );
7438
+ return [...sourceItems].sort(
7439
+ (a, b) => b.createdAt.localeCompare(a.createdAt)
7440
+ );
7441
+ },
7442
+ [activeRoute, isAllQaVisible, items, reviewPathPrefix, sitemapSourceItems]
6457
7443
  );
6458
- const numberedActiveItems = useMemo4(
7444
+ const numberedActiveItems = useMemo5(
6459
7445
  () => getNumberedReviewItems(activeItems, reviewViewportPresets),
6460
7446
  [activeItems, reviewViewportPresets]
6461
7447
  );
6462
- const filteredNumberedActiveItems = useMemo4(
7448
+ const scopeFilteredNumberedActiveItems = useMemo5(
6463
7449
  () => qaFilter === "all" ? numberedActiveItems : numberedActiveItems.filter(
6464
7450
  (numberedItem) => numberedItem.scope === qaFilter
6465
7451
  ),
6466
7452
  [numberedActiveItems, qaFilter]
6467
7453
  );
6468
- const hiddenOverlayItemIdList = useMemo4(
7454
+ const statusFilteredNumberedActiveItems = useMemo5(
7455
+ () => qaStatusFilter === "all" ? numberedActiveItems : numberedActiveItems.filter(
7456
+ (numberedItem) => normalizeReviewItemStatus(numberedItem.item.status) === qaStatusFilter
7457
+ ),
7458
+ [numberedActiveItems, qaStatusFilter]
7459
+ );
7460
+ const filteredNumberedActiveItems = useMemo5(
7461
+ () => qaStatusFilter === "all" ? scopeFilteredNumberedActiveItems : scopeFilteredNumberedActiveItems.filter(
7462
+ (numberedItem) => normalizeReviewItemStatus(numberedItem.item.status) === qaStatusFilter
7463
+ ),
7464
+ [qaStatusFilter, scopeFilteredNumberedActiveItems]
7465
+ );
7466
+ const hiddenOverlayItemIdList = useMemo5(
6469
7467
  () => Array.from(hiddenOverlayItemIds),
6470
7468
  [hiddenOverlayItemIds]
6471
7469
  );
6472
- const qaFilterCounts = useMemo4(() => {
7470
+ const qaFilterCounts = useMemo5(() => {
6473
7471
  const counts = /* @__PURE__ */ new Map();
6474
- counts.set("all", numberedActiveItems.length);
6475
- numberedActiveItems.forEach((numberedItem) => {
7472
+ counts.set("all", statusFilteredNumberedActiveItems.length);
7473
+ statusFilteredNumberedActiveItems.forEach((numberedItem) => {
6476
7474
  counts.set(numberedItem.scope, (counts.get(numberedItem.scope) ?? 0) + 1);
6477
7475
  });
6478
7476
  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
- )
7477
+ }, [statusFilteredNumberedActiveItems]);
7478
+ const qaStatusFilterCounts = useMemo5(() => {
7479
+ const counts = /* @__PURE__ */ new Map();
7480
+ counts.set("all", scopeFilteredNumberedActiveItems.length);
7481
+ scopeFilteredNumberedActiveItems.forEach((numberedItem) => {
7482
+ const status = normalizeReviewItemStatus(numberedItem.item.status);
7483
+ counts.set(status, (counts.get(status) ?? 0) + 1);
7484
+ });
7485
+ return counts;
7486
+ }, [scopeFilteredNumberedActiveItems]);
7487
+ const getItemPreset = useCallback10(
7488
+ (item) => findViewportPreset(
7489
+ viewportPresets,
7490
+ item.viewport?.width ?? 0,
7491
+ item.viewport?.height ?? 0
6487
7492
  ),
6488
7493
  [viewportPresets]
6489
7494
  );
6490
- const presetScopeCounts = useMemo4(() => {
7495
+ const getItemPresetScope = useCallback10(
7496
+ (item) => getViewportPresetKind(getItemPreset(item)),
7497
+ [getItemPreset]
7498
+ );
7499
+ const getItemPresetColumn = useCallback10(
7500
+ (item) => {
7501
+ const preset = getItemPreset(item);
7502
+ const presetIndex = Math.max(0, viewportPresets.indexOf(preset));
7503
+ return createSitemapViewportColumn(preset, presetIndex);
7504
+ },
7505
+ [getItemPreset, viewportPresets]
7506
+ );
7507
+ const getItemCountScope = useCallback10(
7508
+ (item) => item.scope === "dom" ? "dom" : getItemPresetScope(item),
7509
+ [getItemPresetScope]
7510
+ );
7511
+ const activeRemainingItemCount = useMemo5(
7512
+ () => activeItems.filter(
7513
+ (item) => normalizeReviewItemStatus(item.status) !== SITEMAP_STATUS_DONE
7514
+ ).length,
7515
+ [activeItems]
7516
+ );
7517
+ const presetScopeCounts = useMemo5(() => {
6491
7518
  const counts = /* @__PURE__ */ new Map();
6492
7519
  activeItems.forEach((item) => {
6493
7520
  const scope = getItemPresetScope(item);
@@ -6496,7 +7523,7 @@ var useReviewShellData = ({
6496
7523
  return counts;
6497
7524
  }, [activeItems, getItemPresetScope]);
6498
7525
  const currentPresetScope = getViewportPresetKind(size);
6499
- const pageQaCounts = useMemo4(() => {
7526
+ const pageQaCounts = useMemo5(() => {
6500
7527
  const counts = /* @__PURE__ */ new Map();
6501
7528
  const addItems = (sourceKey, sourceItems) => {
6502
7529
  sourceItems.forEach((item) => {
@@ -6504,16 +7531,48 @@ var useReviewShellData = ({
6504
7531
  getItemTarget(item, reviewPathPrefix),
6505
7532
  reviewPathPrefix
6506
7533
  );
6507
- const count = counts.get(pageTarget) ?? createEmptySitemapQaCount();
6508
- count[sourceKey] += 1;
6509
- counts.set(pageTarget, count);
7534
+ const currentCount = counts.get(pageTarget) ?? createEmptySitemapQaCount();
7535
+ const status = normalizeReviewItemStatus(item.status);
7536
+ const scope = getItemCountScope(item);
7537
+ const viewportColumn = getItemPresetColumn(item);
7538
+ const currentViewportCount = currentCount.viewport[viewportColumn.key] ?? { total: 0, remaining: 0 };
7539
+ const isRemaining = status !== SITEMAP_STATUS_DONE;
7540
+ counts.set(pageTarget, {
7541
+ ...currentCount,
7542
+ total: currentCount.total + 1,
7543
+ remaining: isRemaining ? currentCount.remaining + 1 : currentCount.remaining,
7544
+ local: currentCount.local + (sourceKey === "local" ? 1 : 0),
7545
+ remote: currentCount.remote + (sourceKey === "remote" ? 1 : 0),
7546
+ status: {
7547
+ ...currentCount.status,
7548
+ [status]: currentCount.status[status] + 1
7549
+ },
7550
+ scope: {
7551
+ ...currentCount.scope,
7552
+ [scope]: (currentCount.scope[scope] ?? 0) + 1
7553
+ },
7554
+ viewport: {
7555
+ ...currentCount.viewport,
7556
+ [viewportColumn.key]: {
7557
+ total: currentViewportCount.total + 1,
7558
+ remaining: isRemaining ? currentViewportCount.remaining + 1 : currentViewportCount.remaining
7559
+ }
7560
+ }
7561
+ });
6510
7562
  });
6511
7563
  };
6512
7564
  addItems("local", sitemapItems.local);
6513
7565
  addItems("remote", sitemapItems.remote);
6514
7566
  return counts;
6515
- }, [reviewPathPrefix, sitemapItems]);
6516
- const selectedNumberedItem = useMemo4(
7567
+ }, [getItemCountScope, getItemPresetColumn, reviewPathPrefix, sitemapItems]);
7568
+ const allQaCount = useMemo5(
7569
+ () => Array.from(pageQaCounts.values()).reduce(
7570
+ addSitemapQaCounts,
7571
+ createEmptySitemapQaCount()
7572
+ ),
7573
+ [pageQaCounts]
7574
+ );
7575
+ const selectedNumberedItem = useMemo5(
6517
7576
  () => selectedItemId ? numberedActiveItems.find(
6518
7577
  (numberedItem) => numberedItem.item.id === selectedItemId
6519
7578
  ) : void 0,
@@ -6521,6 +7580,8 @@ var useReviewShellData = ({
6521
7580
  );
6522
7581
  return {
6523
7582
  activeItems,
7583
+ activeRemainingItemCount,
7584
+ allQaCount,
6524
7585
  currentPresetScope,
6525
7586
  filteredNumberedActiveItems,
6526
7587
  getItemPresetScope,
@@ -6531,10 +7592,13 @@ var useReviewShellData = ({
6531
7592
  presetScopeCounts,
6532
7593
  qaFilter,
6533
7594
  qaFilterCounts,
7595
+ qaStatusFilter,
7596
+ qaStatusFilterCounts,
6534
7597
  selectedNumberedItem,
6535
7598
  setHiddenOverlayItemIds,
6536
7599
  setItems,
6537
7600
  setQaFilter,
7601
+ setQaStatusFilter,
6538
7602
  setSitemapItems,
6539
7603
  targetSrc
6540
7604
  };
@@ -6635,9 +7699,9 @@ var useReviewShellHotkeys = ({
6635
7699
 
6636
7700
  // src/react-shell/hooks/use.review.shell.state.ts
6637
7701
  import {
6638
- useMemo as useMemo5,
6639
- useRef as useRef3,
6640
- useState as useState7
7702
+ useMemo as useMemo6,
7703
+ useRef as useRef4,
7704
+ useState as useState9
6641
7705
  } from "react";
6642
7706
 
6643
7707
  // src/react-shell/adapters.ts
@@ -6782,11 +7846,11 @@ var useReviewShellState = ({
6782
7846
  reviewPathPrefix
6783
7847
  }) => {
6784
7848
  const viewportPresets = presets.length > 0 ? presets : DEFAULT_REVIEW_VIEWPORT_PRESETS;
6785
- const reviewViewportPresets = useMemo5(
7849
+ const reviewViewportPresets = useMemo6(
6786
7850
  () => toReviewViewportPresets(viewportPresets),
6787
7851
  [viewportPresets]
6788
7852
  );
6789
- const normalizedAdapters = useMemo5(
7853
+ const normalizedAdapters = useMemo6(
6790
7854
  () => normalizeReviewShellAdapters(adapters),
6791
7855
  [adapters]
6792
7856
  );
@@ -6794,7 +7858,7 @@ var useReviewShellState = ({
6794
7858
  const remoteAdapterEntry = normalizedAdapters.remote;
6795
7859
  const sourceEntries = normalizedAdapters.sources;
6796
7860
  const defaultSource = sourceEntries[0]?.label ?? "local";
6797
- const [source, setSource] = useState7(() => {
7861
+ const [source, setSource] = useState9(() => {
6798
7862
  const initialSource = getInitialSource(remoteAdapterEntry?.label);
6799
7863
  return sourceEntries.some((entry) => entry.label === initialSource) ? initialSource : defaultSource;
6800
7864
  });
@@ -6807,40 +7871,40 @@ var useReviewShellState = ({
6807
7871
  const canWriteArea = activeAdapterEntry.writeModes.includes("area");
6808
7872
  const canWriteDom = activeAdapterEntry.writeModes.includes("dom");
6809
7873
  const adapter = activeAdapterEntry.adapter;
6810
- const iframeRef = useRef3(null);
6811
- const frameScrollRef = useRef3(null);
6812
- const controllerRef = useRef3(null);
6813
- const cleanupTargetRef = useRef3(null);
6814
- const pendingRestoreRef = useRef3(null);
6815
- const pendingInitialItemIdRef = useRef3(getInitialItemId());
6816
- const selectedItemIdRef = useRef3(getInitialItemId());
6817
- const hiddenOverlayItemIdListRef = useRef3([]);
6818
- const [target, setTarget] = useState7(
7874
+ const iframeRef = useRef4(null);
7875
+ const frameScrollRef = useRef4(null);
7876
+ const controllerRef = useRef4(null);
7877
+ const cleanupTargetRef = useRef4(null);
7878
+ const pendingRestoreRef = useRef4(null);
7879
+ const pendingInitialItemIdRef = useRef4(getInitialItemId());
7880
+ const selectedItemIdRef = useRef4(getInitialItemId());
7881
+ const hiddenOverlayItemIdListRef = useRef4([]);
7882
+ const [target, setTarget] = useState9(
6819
7883
  () => getInitialTarget(reviewPathPrefix)
6820
7884
  );
6821
- const [draftTarget, setDraftTarget] = useState7(
7885
+ const [draftTarget, setDraftTarget] = useState9(
6822
7886
  () => getInitialTarget(reviewPathPrefix)
6823
7887
  );
6824
- const [activeRoute, setActiveRoute] = useState7(
7888
+ const [activeRoute, setActiveRoute] = useState9(
6825
7889
  () => getInitialTarget(reviewPathPrefix)
6826
7890
  );
6827
- const [size, setSize] = useState7(
7891
+ const [size, setSize] = useState9(
6828
7892
  () => getInitialSize(viewportPresets)
6829
7893
  );
6830
- const [mode, setMode] = useState7("idle");
6831
- const [targetOverlayState, setTargetOverlayState] = useState7({
7894
+ const [mode, setMode] = useState9("idle");
7895
+ const [targetOverlayState, setTargetOverlayState] = useState9({
6832
7896
  grid: false,
6833
7897
  figma: false
6834
7898
  });
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);
6842
- const targetRef = useRef3(target);
6843
- const sizeRef = useRef3(size);
7899
+ const [selectedItemId, setSelectedItemId] = useState9(getInitialItemId());
7900
+ const [isListVisible, setIsListVisible] = useState9(true);
7901
+ const [isSitemapOpen, setIsSitemapOpen] = useState9(false);
7902
+ const [isInitialPromptOpen, setIsInitialPromptOpen] = useState9(false);
7903
+ const [copyLabel, setCopyLabel] = useState9("Copy URL");
7904
+ const [toastMessage, setToastMessage] = useState9("");
7905
+ const [copiedPromptKey, setCopiedPromptKey] = useState9(null);
7906
+ const targetRef = useRef4(target);
7907
+ const sizeRef = useRef4(size);
6844
7908
  const isFigmaOverlayAvailable = getIsFigmaOverlayAvailable(size);
6845
7909
  return {
6846
7910
  activeAdapterEntry,
@@ -7130,12 +8194,15 @@ var removeReviewItem = async ({
7130
8194
  };
7131
8195
 
7132
8196
  // src/react-shell/review/shell.tsx
7133
- import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
8197
+ import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
7134
8198
  var getReviewModeWriteMode = (mode) => {
7135
8199
  if (mode === "element") return "dom";
7136
8200
  if (mode === "note" || mode === "area") return mode;
7137
8201
  return null;
7138
8202
  };
8203
+ var SOURCE_PANEL_MAX_WIDTH = 440;
8204
+ var SOURCE_PANEL_MIN_WIDTH = 240;
8205
+ var SOURCE_PANEL_MAX_HEIGHT = 260;
7139
8206
  var ReviewShell = ({
7140
8207
  projectId,
7141
8208
  pages,
@@ -7143,8 +8210,10 @@ var ReviewShell = ({
7143
8210
  presets = DEFAULT_REVIEW_VIEWPORT_PRESETS,
7144
8211
  ruler,
7145
8212
  initialPrompt = DEFAULT_INITIAL_REVIEW_PROMPT,
8213
+ adjustmentLabel,
7146
8214
  reviewPathPrefix = DEFAULT_REVIEW_PATH_PREFIX,
7147
8215
  sourceRoot,
8216
+ sourceInspector,
7148
8217
  presence
7149
8218
  }) => {
7150
8219
  const {
@@ -7161,7 +8230,7 @@ var ReviewShell = ({
7161
8230
  frameScrollRef,
7162
8231
  hiddenOverlayItemIdListRef,
7163
8232
  iframeRef,
7164
- isFigmaOverlayAvailable,
8233
+ isFigmaOverlayAvailable: isViewportFigmaOverlayAvailable,
7165
8234
  isInitialPromptOpen,
7166
8235
  isListVisible,
7167
8236
  isRemoteSource,
@@ -7203,9 +8272,22 @@ var ReviewShell = ({
7203
8272
  presets,
7204
8273
  reviewPathPrefix
7205
8274
  });
7206
- const sourceShortcutCleanupRef = useRef4(null);
8275
+ const sourceShortcutCleanupRef = useRef5(null);
8276
+ const sourceInspectorInteractionRef = useRef5(false);
8277
+ const [sourceInspectorState, setSourceInspectorState] = useState10(null);
8278
+ const [isAllQaVisible, setIsAllQaVisible] = useState10(false);
8279
+ const sourceOpenOptions = useMemo7(
8280
+ () => ({
8281
+ ...sourceInspector,
8282
+ sourceRoot
8283
+ }),
8284
+ [sourceInspector, sourceRoot]
8285
+ );
8286
+ const isSourceInspectorEnabled = sourceInspector?.enabled !== false;
7207
8287
  const {
7208
8288
  activeItems,
8289
+ activeRemainingItemCount,
8290
+ allQaCount,
7209
8291
  currentPresetScope,
7210
8292
  filteredNumberedActiveItems,
7211
8293
  getItemPresetScope,
@@ -7216,14 +8298,19 @@ var ReviewShell = ({
7216
8298
  presetScopeCounts,
7217
8299
  qaFilter,
7218
8300
  qaFilterCounts,
8301
+ qaStatusFilter,
8302
+ qaStatusFilterCounts,
7219
8303
  selectedNumberedItem,
7220
8304
  setHiddenOverlayItemIds,
7221
8305
  setItems,
7222
8306
  setQaFilter,
8307
+ setQaStatusFilter,
7223
8308
  setSitemapItems,
7224
8309
  targetSrc
7225
8310
  } = useReviewShellData({
7226
8311
  activeRoute,
8312
+ isAllQaVisible,
8313
+ isRemoteSource,
7227
8314
  pages,
7228
8315
  reviewPathPrefix,
7229
8316
  reviewViewportPresets,
@@ -7232,7 +8319,14 @@ var ReviewShell = ({
7232
8319
  target,
7233
8320
  viewportPresets
7234
8321
  });
7235
- const [editingItem, setEditingItem] = useState8(null);
8322
+ const [targetFigmaState, setTargetFigmaState] = useState10(null);
8323
+ const targetFigmaConfig = targetFigmaState?.targetSrc === targetSrc ? targetFigmaState.config : null;
8324
+ const figmaFrameUrl = useMemo7(
8325
+ () => getFigmaFrameUrl(targetFigmaConfig, size),
8326
+ [targetFigmaConfig, size]
8327
+ );
8328
+ const isFigmaOverlayAvailable = isViewportFigmaOverlayAvailable && Boolean(targetFigmaConfig);
8329
+ const [editingItem, setEditingItem] = useState10(null);
7236
8330
  const initialPromptText = initialPrompt.trim();
7237
8331
  const refreshItems = useCallback11(
7238
8332
  () => refreshReviewItems({
@@ -7341,7 +8435,6 @@ var ReviewShell = ({
7341
8435
  });
7342
8436
  const {
7343
8437
  clearSelectedItem,
7344
- closeTargetOverlay,
7345
8438
  initReviewKit,
7346
8439
  reloadReviewKit,
7347
8440
  restoreReviewItem,
@@ -7365,6 +8458,7 @@ var ReviewShell = ({
7365
8458
  reviewUserId,
7366
8459
  reviewViewportPresets,
7367
8460
  ruler,
8461
+ adjustmentLabel,
7368
8462
  selectedItemIdRef,
7369
8463
  size,
7370
8464
  sizeRef,
@@ -7430,18 +8524,39 @@ var ReviewShell = ({
7430
8524
  window.clearTimeout(transitionTimeout);
7431
8525
  };
7432
8526
  }, [isListVisible, size.height, size.width, syncTargetViewport, targetSrc]);
7433
- const applyTarget = () => {
7434
- const normalizedTarget = normalizeTarget(draftTarget, reviewPathPrefix);
8527
+ const applyTarget = async () => {
8528
+ const parsedInput = parseReviewAddressInput(draftTarget, reviewPathPrefix);
8529
+ const normalizedTarget = parsedInput.target;
8530
+ const nextSource = parsedInput.source && sourceEntries.some((entry) => entry.label === parsedInput.source) ? parsedInput.source : source;
8531
+ const nextSize = parsedInput.width && parsedInput.height ? findViewportPreset(
8532
+ viewportPresets,
8533
+ parsedInput.width,
8534
+ parsedInput.height
8535
+ ) : sizeRef.current;
8536
+ const nextAdapter = sourceEntries.find((entry) => entry.label === nextSource) ?? activeAdapterEntry;
8537
+ if (parsedInput.itemId) {
8538
+ const item = await nextAdapter.adapter.get(parsedInput.itemId);
8539
+ if (item) {
8540
+ setIsAllQaVisible(false);
8541
+ setSource(nextSource);
8542
+ restoreReviewItem(item);
8543
+ return;
8544
+ }
8545
+ }
7435
8546
  clearSelectedItem();
8547
+ setIsAllQaVisible(false);
8548
+ setSource(nextSource);
7436
8549
  targetRef.current = normalizedTarget;
7437
8550
  setActiveRoute(normalizedTarget);
7438
8551
  setDraftTarget(normalizedTarget);
8552
+ setSize(nextSize);
7439
8553
  setTarget(normalizedTarget);
7440
- updateShellUrl(normalizedTarget, sizeRef.current, source);
8554
+ updateShellUrl(normalizedTarget, nextSize, nextSource);
7441
8555
  };
7442
8556
  const selectPage = (href) => {
7443
8557
  const normalizedTarget = normalizeTarget(href, reviewPathPrefix);
7444
8558
  clearSelectedItem();
8559
+ setIsAllQaVisible(false);
7445
8560
  targetRef.current = normalizedTarget;
7446
8561
  setActiveRoute(normalizedTarget);
7447
8562
  setDraftTarget(normalizedTarget);
@@ -7449,13 +8564,14 @@ var ReviewShell = ({
7449
8564
  updateShellUrl(normalizedTarget, sizeRef.current, source);
7450
8565
  setIsSitemapOpen(false);
7451
8566
  };
8567
+ const selectAllQa = () => {
8568
+ setIsAllQaVisible(true);
8569
+ setIsSitemapOpen(false);
8570
+ };
7452
8571
  const setReviewMode = (nextMode) => {
7453
8572
  const writeMode = getReviewModeWriteMode(nextMode);
7454
8573
  if (writeMode && !activeAdapterEntry.writeModes.includes(writeMode)) return;
7455
8574
  closeRuler();
7456
- if (nextMode === "element") {
7457
- closeTargetOverlay("figma");
7458
- }
7459
8575
  setControllerReviewMode(nextMode);
7460
8576
  };
7461
8577
  useReviewShellHotkeys({
@@ -7486,6 +8602,139 @@ var ReviewShell = ({
7486
8602
  },
7487
8603
  [setToastMessage]
7488
8604
  );
8605
+ const refreshTargetFigmaConfig = useCallback11(() => {
8606
+ const config = getTargetFigmaFrameConfig(
8607
+ iframeRef.current?.contentWindow
8608
+ );
8609
+ setTargetFigmaState(config ? { targetSrc, config } : null);
8610
+ }, [iframeRef, targetSrc]);
8611
+ useEffect10(() => {
8612
+ const targetDocument = iframeRef.current?.contentDocument;
8613
+ setTargetFigmaOverlayLocked(targetDocument, mode === "element");
8614
+ return () => {
8615
+ setTargetFigmaOverlayLocked(targetDocument, false);
8616
+ };
8617
+ }, [iframeRef, mode, targetSrc]);
8618
+ const clearSourceInspector = useCallback11(() => {
8619
+ sourceInspectorInteractionRef.current = false;
8620
+ setSourceInspectorState(null);
8621
+ }, []);
8622
+ const getSourceInspectorRect = useCallback11(
8623
+ (element) => {
8624
+ const frame = iframeRef.current;
8625
+ if (!frame) return null;
8626
+ const frameRect = frame.getBoundingClientRect();
8627
+ const elementRect = element.getBoundingClientRect();
8628
+ const left = Math.max(frameRect.left, frameRect.left + elementRect.left);
8629
+ const top = Math.max(frameRect.top, frameRect.top + elementRect.top);
8630
+ const right = Math.min(
8631
+ frameRect.right,
8632
+ frameRect.left + elementRect.right
8633
+ );
8634
+ const bottom = Math.min(
8635
+ frameRect.bottom,
8636
+ frameRect.top + elementRect.bottom
8637
+ );
8638
+ return {
8639
+ height: Math.max(2, bottom - top),
8640
+ left,
8641
+ top,
8642
+ width: Math.max(2, right - left)
8643
+ };
8644
+ },
8645
+ [iframeRef]
8646
+ );
8647
+ const getSourceInspectorPanelPosition = useCallback11(
8648
+ (rect) => {
8649
+ const margin = 12;
8650
+ const gap = 10;
8651
+ const preferredLeft = rect.left + rect.width + gap;
8652
+ const rightSpace = window.innerWidth - preferredLeft - margin;
8653
+ const leftSpace = rect.left - gap - margin;
8654
+ const canOpenRight = rightSpace >= SOURCE_PANEL_MIN_WIDTH;
8655
+ const canOpenLeft = leftSpace >= SOURCE_PANEL_MIN_WIDTH;
8656
+ const left = canOpenRight || !canOpenLeft ? preferredLeft : margin;
8657
+ const right = canOpenRight || !canOpenLeft ? null : Math.max(margin, window.innerWidth - (rect.left - gap));
8658
+ const maxWidth = Math.min(
8659
+ SOURCE_PANEL_MAX_WIDTH,
8660
+ Math.max(
8661
+ SOURCE_PANEL_MIN_WIDTH,
8662
+ canOpenRight ? rightSpace : canOpenLeft ? leftSpace : window.innerWidth - margin * 2
8663
+ )
8664
+ );
8665
+ const top = Math.min(
8666
+ Math.max(margin, rect.top),
8667
+ Math.max(margin, window.innerHeight - SOURCE_PANEL_MAX_HEIGHT - margin)
8668
+ );
8669
+ return { left, maxWidth, right, top };
8670
+ },
8671
+ []
8672
+ );
8673
+ const showSourceInspectorForTarget = useCallback11(
8674
+ (target2, isPinned = false) => {
8675
+ const candidates = getSourceCandidates(target2).map((candidate) => ({
8676
+ ...candidate,
8677
+ openUrl: getSourceOpenUrl(candidate.source, {
8678
+ ...sourceOpenOptions,
8679
+ omitPosition: !candidate.usesPosition
8680
+ })
8681
+ }));
8682
+ const firstCandidate = candidates[0];
8683
+ const rect = firstCandidate ? getSourceInspectorRect(firstCandidate.element) : null;
8684
+ if (!firstCandidate || !rect) {
8685
+ setSourceInspectorState(null);
8686
+ return [];
8687
+ }
8688
+ const { left, maxWidth, right, top } = getSourceInspectorPanelPosition(rect);
8689
+ setSourceInspectorState({
8690
+ candidates,
8691
+ isPinned,
8692
+ panelLeft: left,
8693
+ panelMaxWidth: maxWidth,
8694
+ panelRight: right,
8695
+ panelTop: top,
8696
+ rect
8697
+ });
8698
+ return candidates;
8699
+ },
8700
+ [
8701
+ getSourceInspectorPanelPosition,
8702
+ getSourceInspectorRect,
8703
+ sourceOpenOptions
8704
+ ]
8705
+ );
8706
+ const showSourceOutlineForTarget = useCallback11(
8707
+ (target2) => {
8708
+ const firstCandidate = getSourceCandidates(target2)[0];
8709
+ const rect = firstCandidate ? getSourceInspectorRect(firstCandidate.element) : null;
8710
+ if (!firstCandidate || !rect) {
8711
+ setSourceInspectorState(null);
8712
+ return null;
8713
+ }
8714
+ setSourceInspectorState({
8715
+ candidates: [],
8716
+ isPinned: false,
8717
+ panelLeft: 0,
8718
+ panelMaxWidth: SOURCE_PANEL_MAX_WIDTH,
8719
+ panelRight: null,
8720
+ panelTop: 0,
8721
+ rect
8722
+ });
8723
+ return firstCandidate;
8724
+ },
8725
+ [getSourceInspectorRect]
8726
+ );
8727
+ const openSourceCandidate = useCallback11(
8728
+ (candidate) => {
8729
+ const didOpen = openSourceInEditor(candidate.source, {
8730
+ ...sourceOpenOptions,
8731
+ omitPosition: !candidate.usesPosition
8732
+ });
8733
+ showToast(didOpen ? "Source opened" : "Source root required");
8734
+ clearSourceInspector();
8735
+ },
8736
+ [clearSourceInspector, showToast, sourceOpenOptions]
8737
+ );
7489
8738
  const cleanupSourceOpenShortcut = useCallback11(() => {
7490
8739
  sourceShortcutCleanupRef.current?.();
7491
8740
  sourceShortcutCleanupRef.current = null;
@@ -7498,8 +8747,10 @@ var ReviewShell = ({
7498
8747
  } catch {
7499
8748
  return;
7500
8749
  }
7501
- if (!frameDocument) return;
7502
- const hoverAttribute = "data-dfwr-source-hover";
8750
+ if (!frameDocument || !isSourceInspectorEnabled) return;
8751
+ const frameRoot = frameDocument.head ?? frameDocument.documentElement;
8752
+ const frameBody = frameDocument.body ?? frameDocument.documentElement;
8753
+ if (!frameRoot || !frameBody) return;
7503
8754
  const optionAttribute = "data-dfwr-source-option";
7504
8755
  const fontOverlayAttribute = "data-dfwr-source-fonts";
7505
8756
  const style = frameDocument.createElement("style");
@@ -7510,6 +8761,13 @@ var ReviewShell = ({
7510
8761
  cursor: crosshair !important;
7511
8762
  }
7512
8763
 
8764
+ html[${optionAttribute}="true"] .helper-figma-root,
8765
+ html[${optionAttribute}="true"] .helper-figma-root *,
8766
+ html[${optionAttribute}="true"] .helper-figma-loading-backdrop,
8767
+ html[${optionAttribute}="true"] .helper-figma-loading-backdrop * {
8768
+ pointer-events: none !important;
8769
+ }
8770
+
7513
8771
  html[${optionAttribute}="true"] body::before {
7514
8772
  position: fixed !important;
7515
8773
  z-index: 2147483647 !important;
@@ -7528,17 +8786,13 @@ var ReviewShell = ({
7528
8786
  pointer-events: none !important;
7529
8787
  }
7530
8788
 
7531
- [${hoverAttribute}="true"] {
7532
- outline: 2px solid rgba(124, 199, 255, 0.96) !important;
7533
- outline-offset: 2px !important;
7534
- }
7535
-
7536
8789
  [${fontOverlayAttribute}] {
7537
8790
  position: fixed !important;
7538
8791
  z-index: 2147483647 !important;
7539
8792
  display: flex !important;
7540
8793
  flex-direction: column !important;
7541
- max-width: 180px !important;
8794
+ width: max-content !important;
8795
+ max-width: calc(100vw - 8px) !important;
7542
8796
  border: 1px solid rgba(124, 199, 255, 0.72) !important;
7543
8797
  border-radius: 6px !important;
7544
8798
  padding: 4px 6px !important;
@@ -7546,28 +8800,36 @@ var ReviewShell = ({
7546
8800
  background: rgba(15, 23, 42, 0.9) !important;
7547
8801
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.28) !important;
7548
8802
  font: 800 11px/1.35 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace !important;
8803
+ overflow-wrap: anywhere !important;
7549
8804
  pointer-events: none !important;
7550
- white-space: nowrap !important;
8805
+ white-space: normal !important;
7551
8806
  }
7552
8807
 
7553
8808
  [${fontOverlayAttribute}] > span {
7554
- display: flex !important;
8809
+ display: grid !important;
8810
+ grid-template-columns: auto minmax(0, 1fr) !important;
7555
8811
  justify-content: space-between !important;
7556
8812
  gap: 10px !important;
7557
8813
  }
7558
8814
 
8815
+ [${fontOverlayAttribute}] > span > span:last-child {
8816
+ min-width: 0 !important;
8817
+ text-align: right !important;
8818
+ }
8819
+
7559
8820
  [${fontOverlayAttribute}][hidden] {
7560
8821
  display: none !important;
7561
8822
  }
7562
8823
  `;
7563
- (frameDocument.head ?? frameDocument.documentElement).append(style);
8824
+ frameRoot.append(style);
7564
8825
  const fontOverlay = frameDocument.createElement("div");
7565
8826
  fontOverlay.setAttribute(fontOverlayAttribute, "true");
7566
8827
  fontOverlay.hidden = true;
7567
- (frameDocument.body ?? frameDocument.documentElement).append(fontOverlay);
8828
+ frameBody.append(fontOverlay);
7568
8829
  let hoveredElement = null;
7569
- let lastSourceElement = null;
8830
+ let lastSourceTarget = null;
7570
8831
  let isSourceSelecting = false;
8832
+ let isSourcePanelPinned = false;
7571
8833
  const getFontHints = (element) => {
7572
8834
  if (!element) return [];
7573
8835
  const values = [];
@@ -7591,10 +8853,13 @@ var ReviewShell = ({
7591
8853
  const rect = element.getBoundingClientRect();
7592
8854
  const frameWidth = frameDocument.documentElement.clientWidth;
7593
8855
  const showAbove = rect.top > 48;
7594
- const left = Math.max(4, Math.min(rect.left, frameWidth - 96));
7595
8856
  const top = Math.max(4, showAbove ? rect.top : rect.bottom);
7596
8857
  fontOverlay.replaceChildren();
7597
8858
  fontOverlay.style.minWidth = "72px";
8859
+ fontOverlay.style.left = "4px";
8860
+ fontOverlay.style.top = `${top}px`;
8861
+ fontOverlay.style.transform = showAbove ? "translateY(calc(-100% - 6px))" : "translateY(6px)";
8862
+ fontOverlay.style.visibility = "hidden";
7598
8863
  const rows = values.map(({ tag, value }) => {
7599
8864
  const row = frameDocument.createElement("span");
7600
8865
  const tagText = frameDocument.createElement("span");
@@ -7605,54 +8870,70 @@ var ReviewShell = ({
7605
8870
  return row;
7606
8871
  });
7607
8872
  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
8873
  fontOverlay.hidden = false;
8874
+ const overlayWidth = fontOverlay.getBoundingClientRect().width;
8875
+ const left = Math.max(
8876
+ 4,
8877
+ Math.min(rect.left, frameWidth - overlayWidth - 4)
8878
+ );
8879
+ fontOverlay.style.left = `${left}px`;
8880
+ fontOverlay.style.visibility = "";
7612
8881
  };
7613
8882
  const setHoveredElement = (element) => {
7614
- if (hoveredElement !== element) {
7615
- hoveredElement?.removeAttribute(hoverAttribute);
7616
- hoveredElement = element;
7617
- hoveredElement?.setAttribute(hoverAttribute, "true");
7618
- }
8883
+ hoveredElement = element;
7619
8884
  updateFontOverlay(element);
7620
8885
  };
7621
8886
  const setSourceSelecting = (isSelecting) => {
7622
8887
  isSourceSelecting = isSelecting;
7623
8888
  if (isSelecting) {
8889
+ isSourcePanelPinned = false;
7624
8890
  frameDocument.documentElement.setAttribute(optionAttribute, "true");
7625
- setHoveredElement(lastSourceElement);
8891
+ const candidate = showSourceOutlineForTarget(lastSourceTarget);
8892
+ setHoveredElement(candidate?.element ?? hoveredElement);
7626
8893
  return;
7627
8894
  }
7628
8895
  setHoveredElement(null);
7629
8896
  fontOverlay.hidden = true;
7630
8897
  frameDocument.documentElement.removeAttribute(optionAttribute);
8898
+ if (!isSourcePanelPinned && !sourceInspectorInteractionRef.current) {
8899
+ clearSourceInspector();
8900
+ }
7631
8901
  };
7632
8902
  const handleMouseMove = (event) => {
7633
- lastSourceElement = getSourceHintElement(event.target);
8903
+ lastSourceTarget = event.target;
8904
+ const candidates = getSourceCandidates(event.target);
8905
+ const sourceElement = candidates[0]?.element ?? null;
7634
8906
  if (event.altKey && !isSourceSelecting) {
7635
8907
  setSourceSelecting(true);
7636
8908
  }
7637
- setHoveredElement(isSourceSelecting ? lastSourceElement : null);
8909
+ if (isSourceSelecting && !isSourcePanelPinned) {
8910
+ showSourceOutlineForTarget(event.target);
8911
+ }
8912
+ setHoveredElement(isSourceSelecting ? sourceElement : null);
7638
8913
  };
7639
8914
  const handleClick = (event) => {
7640
8915
  if (!isSourceSelecting && !event.altKey) return;
7641
8916
  event.preventDefault();
7642
8917
  event.stopPropagation();
7643
8918
  event.stopImmediatePropagation();
7644
- const source2 = getElementSourceHint(event.target);
7645
- if (!source2?.file) {
8919
+ const candidates = showSourceInspectorForTarget(event.target, true);
8920
+ if (!candidates.length) {
7646
8921
  showToast("Source hint not found");
8922
+ isSourcePanelPinned = false;
7647
8923
  setSourceSelecting(false);
7648
8924
  return;
7649
8925
  }
7650
- const didOpen = openSourceInEditor(source2, sourceRoot);
7651
- showToast(didOpen ? "Source opened" : "Source root required");
8926
+ isSourcePanelPinned = true;
7652
8927
  setSourceSelecting(false);
7653
8928
  };
7654
8929
  const isOptionKeyEvent = (event) => event.key === "Alt" || event.code === "AltLeft" || event.code === "AltRight" || event.altKey;
7655
8930
  const handleKeyDown = (event) => {
8931
+ if (event.key === "Escape") {
8932
+ isSourcePanelPinned = false;
8933
+ setSourceSelecting(false);
8934
+ clearSourceInspector();
8935
+ return;
8936
+ }
7656
8937
  if (!isOptionKeyEvent(event)) return;
7657
8938
  cancelReviewMode();
7658
8939
  setSourceSelecting(true);
@@ -7661,43 +8942,70 @@ var ReviewShell = ({
7661
8942
  if (isOptionKeyEvent(event) || !event.altKey) setSourceSelecting(false);
7662
8943
  };
7663
8944
  const handleBlur = () => {
8945
+ isSourcePanelPinned = false;
8946
+ setSourceSelecting(false);
8947
+ };
8948
+ const handleWindowPointerDown = (event) => {
8949
+ const target2 = event.target;
8950
+ if (target2 instanceof Element && target2.closest(".df-review-source-popover")) {
8951
+ sourceInspectorInteractionRef.current = true;
8952
+ return;
8953
+ }
8954
+ isSourcePanelPinned = false;
8955
+ sourceInspectorInteractionRef.current = false;
7664
8956
  setSourceSelecting(false);
8957
+ clearSourceInspector();
7665
8958
  };
7666
8959
  frameDocument.addEventListener("mousemove", handleMouseMove, true);
7667
8960
  frameDocument.addEventListener("click", handleClick, true);
7668
8961
  frameDocument.addEventListener("keydown", handleKeyDown, true);
7669
8962
  frameDocument.addEventListener("keyup", handleKeyUp, true);
7670
- frameDocument.defaultView?.addEventListener("blur", handleBlur);
7671
8963
  window.addEventListener("keydown", handleKeyDown, true);
7672
8964
  window.addEventListener("keyup", handleKeyUp, true);
7673
8965
  window.addEventListener("blur", handleBlur);
8966
+ window.addEventListener("pointerdown", handleWindowPointerDown, true);
7674
8967
  sourceShortcutCleanupRef.current = () => {
7675
8968
  frameDocument.removeEventListener("mousemove", handleMouseMove, true);
7676
8969
  frameDocument.removeEventListener("click", handleClick, true);
7677
8970
  frameDocument.removeEventListener("keydown", handleKeyDown, true);
7678
8971
  frameDocument.removeEventListener("keyup", handleKeyUp, true);
7679
- frameDocument.defaultView?.removeEventListener("blur", handleBlur);
7680
8972
  window.removeEventListener("keydown", handleKeyDown, true);
7681
8973
  window.removeEventListener("keyup", handleKeyUp, true);
7682
8974
  window.removeEventListener("blur", handleBlur);
8975
+ window.removeEventListener("pointerdown", handleWindowPointerDown, true);
8976
+ isSourcePanelPinned = false;
7683
8977
  setSourceSelecting(false);
7684
8978
  style.remove();
7685
8979
  fontOverlay.remove();
7686
8980
  };
7687
8981
  }, [
7688
8982
  cancelReviewMode,
8983
+ clearSourceInspector,
7689
8984
  cleanupSourceOpenShortcut,
7690
8985
  iframeRef,
8986
+ isSourceInspectorEnabled,
7691
8987
  showToast,
7692
- sourceRoot
8988
+ showSourceOutlineForTarget,
8989
+ showSourceInspectorForTarget
7693
8990
  ]);
7694
8991
  useEffect10(() => {
7695
8992
  return cleanupSourceOpenShortcut;
7696
8993
  }, [cleanupSourceOpenShortcut]);
7697
8994
  const loadTargetFrame = useCallback11(() => {
7698
8995
  initReviewKit();
8996
+ refreshTargetFigmaConfig();
8997
+ setTargetFigmaOverlayLocked(
8998
+ iframeRef.current?.contentDocument,
8999
+ mode === "element"
9000
+ );
7699
9001
  bindSourceOpenShortcut();
7700
- }, [bindSourceOpenShortcut, initReviewKit]);
9002
+ }, [
9003
+ bindSourceOpenShortcut,
9004
+ iframeRef,
9005
+ initReviewKit,
9006
+ mode,
9007
+ refreshTargetFigmaConfig
9008
+ ]);
7701
9009
  useEffect10(() => {
7702
9010
  const frame = window.requestAnimationFrame(bindSourceOpenShortcut);
7703
9011
  return () => window.cancelAnimationFrame(frame);
@@ -7752,6 +9060,19 @@ var ReviewShell = ({
7752
9060
  `qa:${numberedItem.item.id}`,
7753
9061
  "QA prompt copied"
7754
9062
  );
9063
+ const copyItemLink = (numberedItem) => {
9064
+ const { item } = numberedItem;
9065
+ return copyPrompt(
9066
+ getShellUrlForItem(
9067
+ getItemTarget(item, reviewPathPrefix),
9068
+ getRestoredSize(item, viewportPresets),
9069
+ item.id,
9070
+ source
9071
+ ).href,
9072
+ `link:${item.id}`,
9073
+ "QA link copied"
9074
+ );
9075
+ };
7755
9076
  const removeItem = (item) => removeReviewItem({
7756
9077
  activeAdapterEntry,
7757
9078
  isRemoteSource,
@@ -7794,15 +9115,25 @@ var ReviewShell = ({
7794
9115
  onOpenSettings: openFigmaSettings
7795
9116
  }
7796
9117
  ),
9118
+ currentPagePresenceUsers.length > 0 && /* @__PURE__ */ jsx17("div", { className: "df-review-presence-row", children: /* @__PURE__ */ jsx17(
9119
+ PresenceOverlay,
9120
+ {
9121
+ presenceSessionId,
9122
+ users: currentPagePresenceUsers
9123
+ }
9124
+ ) }),
7797
9125
  isSitemapOpen && /* @__PURE__ */ jsx17(
7798
9126
  SitemapModal,
7799
9127
  {
7800
9128
  pages,
7801
9129
  activeRoute,
9130
+ allQaCount,
9131
+ isAllQaVisible,
7802
9132
  pageQaCounts,
7803
9133
  pagePresenceUsers,
7804
9134
  getPageTarget: (href) => normalizeTarget(href, reviewPathPrefix),
7805
9135
  onClose: () => setIsSitemapOpen(false),
9136
+ onSelectAllQa: selectAllQa,
7806
9137
  onSelectPage: selectPage
7807
9138
  }
7808
9139
  ),
@@ -7861,29 +9192,32 @@ var ReviewShell = ({
7861
9192
  {
7862
9193
  activeAdapterEntry,
7863
9194
  activeItems,
7864
- currentPagePresenceUsers,
9195
+ activeRemainingItemCount,
7865
9196
  currentPresetScope,
7866
9197
  filteredNumberedActiveItems,
7867
9198
  getItemPresetScope,
7868
9199
  hiddenOverlayItemIds,
7869
9200
  isListVisible,
9201
+ isAllQaVisible,
7870
9202
  isRemoteSource,
7871
- presenceSessionId,
7872
9203
  copiedPromptKey,
7873
9204
  qaFilter,
7874
9205
  qaFilterCounts,
9206
+ qaStatusFilter,
9207
+ qaStatusFilterCounts,
7875
9208
  remoteAdapterEntry,
7876
9209
  selectedItemId,
7877
9210
  showSourceSelect,
7878
- sourceRoot,
7879
9211
  source,
7880
9212
  sourceEntries,
7881
9213
  onChangeItemStatus: changeItemStatus,
7882
9214
  onClearSelectedItem: clearSelectedReviewItem,
7883
9215
  onChangeReviewSource: changeReviewSource,
9216
+ onCopyItemLink: (numberedItem) => void copyItemLink(numberedItem),
7884
9217
  onCopyItemPrompt: (numberedItem) => void copyItemPrompt(numberedItem),
7885
9218
  onEditItem: setEditingItem,
7886
9219
  onQaFilterChange: setQaFilter,
9220
+ onQaStatusFilterChange: setQaStatusFilter,
7887
9221
  onRefreshReviewData: refreshReviewData2,
7888
9222
  onRemoveItem: removeItem,
7889
9223
  onRestoreReviewItem: restoreReviewItem,
@@ -7896,6 +9230,7 @@ var ReviewShell = ({
7896
9230
  {
7897
9231
  canWriteArea,
7898
9232
  canWriteDom,
9233
+ figmaFrameUrl,
7899
9234
  frameScrollRef,
7900
9235
  iframeRef,
7901
9236
  isRulerAvailable,
@@ -7914,7 +9249,72 @@ var ReviewShell = ({
7914
9249
  onLoadTarget: loadTargetFrame,
7915
9250
  onSetReviewMode: setReviewMode
7916
9251
  }
7917
- )
9252
+ ),
9253
+ sourceInspectorState && /* @__PURE__ */ jsxs15(Fragment4, { children: [
9254
+ /* @__PURE__ */ jsx17(
9255
+ "div",
9256
+ {
9257
+ className: `df-review-source-outline${sourceInspectorState.isPinned ? " is-pinned" : ""}`,
9258
+ style: {
9259
+ height: `${sourceInspectorState.rect.height}px`,
9260
+ left: `${sourceInspectorState.rect.left}px`,
9261
+ top: `${sourceInspectorState.rect.top}px`,
9262
+ width: `${sourceInspectorState.rect.width}px`
9263
+ }
9264
+ }
9265
+ ),
9266
+ sourceInspectorState.candidates.length > 0 && /* @__PURE__ */ jsxs15(
9267
+ "div",
9268
+ {
9269
+ className: `df-review-source-popover${sourceInspectorState.isPinned ? " is-pinned" : ""}`,
9270
+ style: {
9271
+ left: sourceInspectorState.panelRight === null ? `${sourceInspectorState.panelLeft}px` : void 0,
9272
+ maxWidth: `${sourceInspectorState.panelMaxWidth}px`,
9273
+ right: sourceInspectorState.panelRight === null ? void 0 : `${sourceInspectorState.panelRight}px`,
9274
+ top: `${sourceInspectorState.panelTop}px`
9275
+ },
9276
+ onPointerDown: () => {
9277
+ sourceInspectorInteractionRef.current = true;
9278
+ },
9279
+ onPointerEnter: () => {
9280
+ sourceInspectorInteractionRef.current = true;
9281
+ },
9282
+ onPointerLeave: () => {
9283
+ sourceInspectorInteractionRef.current = false;
9284
+ },
9285
+ onClick: (event) => event.stopPropagation(),
9286
+ children: [
9287
+ /* @__PURE__ */ jsx17("div", { className: "df-review-source-popover-close", children: /* @__PURE__ */ jsx17(
9288
+ "button",
9289
+ {
9290
+ "aria-label": "Close source candidates",
9291
+ type: "button",
9292
+ onClick: clearSourceInspector,
9293
+ children: "\xD7"
9294
+ }
9295
+ ) }),
9296
+ /* @__PURE__ */ jsx17("div", { className: "df-review-source-candidate-list", children: sourceInspectorState.candidates.map((candidate) => /* @__PURE__ */ jsx17(
9297
+ "button",
9298
+ {
9299
+ className: "df-review-source-candidate",
9300
+ type: "button",
9301
+ onClick: (event) => {
9302
+ event.preventDefault();
9303
+ event.stopPropagation();
9304
+ openSourceCandidate(candidate);
9305
+ },
9306
+ children: /* @__PURE__ */ jsxs15("span", { className: "df-review-source-candidate-main", children: [
9307
+ /* @__PURE__ */ jsx17("strong", { children: candidate.label }),
9308
+ /* @__PURE__ */ jsx17("span", { children: candidate.filePath }),
9309
+ /* @__PURE__ */ jsx17("small", { children: candidate.positionLabel || (candidate.usesPosition ? "" : "file only") })
9310
+ ] })
9311
+ },
9312
+ candidate.id
9313
+ )) })
9314
+ ]
9315
+ }
9316
+ )
9317
+ ] })
7918
9318
  ]
7919
9319
  }
7920
9320
  );
@@ -8128,15 +9528,16 @@ lucide-react/dist/esm/shared/src/utils/hasA11yProp.mjs:
8128
9528
  lucide-react/dist/esm/context.mjs:
8129
9529
  lucide-react/dist/esm/Icon.mjs:
8130
9530
  lucide-react/dist/esm/createLucideIcon.mjs:
9531
+ lucide-react/dist/esm/icons/bot.mjs:
8131
9532
  lucide-react/dist/esm/icons/circle-question-mark.mjs:
8132
9533
  lucide-react/dist/esm/icons/copy.mjs:
8133
9534
  lucide-react/dist/esm/icons/external-link.mjs:
8134
9535
  lucide-react/dist/esm/icons/eye-off.mjs:
8135
9536
  lucide-react/dist/esm/icons/eye.mjs:
8136
- lucide-react/dist/esm/icons/file-code-corner.mjs:
8137
9537
  lucide-react/dist/esm/icons/grip-vertical.mjs:
8138
9538
  lucide-react/dist/esm/icons/image.mjs:
8139
9539
  lucide-react/dist/esm/icons/layout-grid.mjs:
9540
+ lucide-react/dist/esm/icons/link-2.mjs:
8140
9541
  lucide-react/dist/esm/icons/list-filter.mjs:
8141
9542
  lucide-react/dist/esm/icons/map.mjs:
8142
9543
  lucide-react/dist/esm/icons/maximize-2.mjs:
@@ -8151,7 +9552,6 @@ lucide-react/dist/esm/icons/smartphone.mjs:
8151
9552
  lucide-react/dist/esm/icons/square-mouse-pointer.mjs:
8152
9553
  lucide-react/dist/esm/icons/sticky-note.mjs:
8153
9554
  lucide-react/dist/esm/icons/upload.mjs:
8154
- lucide-react/dist/esm/icons/users.mjs:
8155
9555
  lucide-react/dist/esm/icons/x.mjs:
8156
9556
  lucide-react/dist/esm/lucide-react.mjs:
8157
9557
  (**