@designfever/web-review-kit 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -101,10 +101,10 @@ function ensureReviewShellStyle() {
101
101
  --df-review-font-size-lg: 14px;
102
102
  --df-review-font-size-xl: 15px;
103
103
  --df-review-font-size-2xl: 18px;
104
- --df-review-font-weight-medium: 650;
105
- --df-review-font-weight-bold: 700;
106
- --df-review-font-weight-strong: 800;
107
- --df-review-font-weight-heavy: 900;
104
+ --df-review-font-weight-medium: 400;
105
+ --df-review-font-weight-bold: 500;
106
+ --df-review-font-weight-strong: 500;
107
+ --df-review-font-weight-heavy: 600;
108
108
  --df-review-line-height-tight: 1.25;
109
109
  --df-review-line-height-base: 1.42;
110
110
  --df-review-line-height-relaxed: 1.55;
@@ -121,7 +121,7 @@ function ensureReviewShellStyle() {
121
121
  --df-review-space-8: 32px;
122
122
  --df-review-control-height-sm: 32px;
123
123
  --df-review-control-height-md: 34px;
124
- --df-review-control-height-lg: 38px;
124
+ --df-review-control-height-lg: 34px;
125
125
  --df-review-radius-xs: 3px;
126
126
  --df-review-radius-sm: 6px;
127
127
  --df-review-radius-md: 8px;
@@ -185,6 +185,7 @@ function ensureReviewShellStyle() {
185
185
 
186
186
  /* Semantic aliases consumed by the existing shell chrome. */
187
187
  --df-review-bg: var(--df-review-color-canvas);
188
+ --df-review-surface: var(--df-review-color-surface);
188
189
  --df-review-topbar: var(--df-review-color-surface);
189
190
  --df-review-panel: var(--df-review-color-panel);
190
191
  --df-review-panel-strong: var(--df-review-color-panel-strong);
@@ -294,7 +295,7 @@ function ensureReviewShellStyle() {
294
295
  .df-review-shell {
295
296
  --df-review-frame-gutter-x: var(--df-review-space-4);
296
297
  display: grid;
297
- grid-template-columns: minmax(0, 1fr) 0 32px;
298
+ grid-template-columns: minmax(0, 1fr) 0 48px;
298
299
  grid-template-rows: auto auto minmax(0, 1fr);
299
300
  width: 100%;
300
301
  height: 100%;
@@ -303,7 +304,7 @@ function ensureReviewShellStyle() {
303
304
  }
304
305
 
305
306
  .df-review-shell.is-list-visible {
306
- grid-template-columns: minmax(0, 1fr) clamp(320px, 28vw, 420px) 32px;
307
+ grid-template-columns: minmax(0, 1fr) clamp(320px, 28vw, 420px) 48px;
307
308
  }
308
309
 
309
310
  .df-review-topbar {
@@ -322,20 +323,6 @@ function ensureReviewShellStyle() {
322
323
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.025);
323
324
  }
324
325
 
325
- .df-review-presence-row {
326
- grid-column: 1;
327
- grid-row: 2;
328
- position: relative;
329
- z-index: 590;
330
- display: flex;
331
- align-items: center;
332
- justify-content: flex-start;
333
- min-width: 0;
334
- padding: var(--df-review-space-2) var(--df-review-frame-gutter-x);
335
- border-bottom: 1px solid var(--df-review-line-soft);
336
- background: var(--df-review-panel);
337
- }
338
-
339
326
  .df-review-address {
340
327
  display: grid;
341
328
  grid-template-columns: auto minmax(160px, 1fr) auto auto;
@@ -348,6 +335,7 @@ function ensureReviewShellStyle() {
348
335
 
349
336
  .df-review-address input {
350
337
  width: 100%;
338
+ height: var(--df-review-control-height-md);
351
339
  min-height: var(--df-review-control-height-md);
352
340
  border: 1px solid var(--df-review-line);
353
341
  border-radius: var(--df-review-radius-sm);
@@ -365,7 +353,6 @@ function ensureReviewShellStyle() {
365
353
  }
366
354
 
367
355
  .df-review-address button,
368
- .df-review-side-toggle,
369
356
  .df-review-presets button,
370
357
  .df-review-overlay-button,
371
358
  .df-review-mode-button,
@@ -374,6 +361,7 @@ function ensureReviewShellStyle() {
374
361
  .df-review-settings-actions button,
375
362
  .df-review-prompt-block-header button,
376
363
  .df-review-item-actions button {
364
+ height: var(--df-review-control-height-md);
377
365
  min-height: var(--df-review-control-height-md);
378
366
  border: 1px solid var(--df-review-line);
379
367
  border-radius: var(--df-review-radius-sm);
@@ -381,12 +369,11 @@ function ensureReviewShellStyle() {
381
369
  box-shadow: var(--df-review-shadow-control);
382
370
  color: var(--df-review-text);
383
371
  font-size: var(--df-review-font-size-sm);
384
- font-weight: 700;
372
+ font-weight: 500;
385
373
  transition: border-color 140ms ease, background 140ms ease, color 140ms ease, box-shadow 140ms ease, opacity 140ms ease;
386
374
  }
387
375
 
388
376
  .df-review-address button:hover,
389
- .df-review-side-toggle:hover,
390
377
  .df-review-presets button:hover,
391
378
  .df-review-overlay-button:hover,
392
379
  .df-review-mode-button:hover,
@@ -487,12 +474,13 @@ function ensureReviewShellStyle() {
487
474
 
488
475
  .df-review-sitemap-header strong {
489
476
  font-size: var(--df-review-font-size-lg);
477
+ font-weight: 600;
490
478
  }
491
479
 
492
480
  .df-review-sitemap-header span {
493
481
  color: var(--df-review-muted);
494
482
  font-size: var(--df-review-font-size-sm);
495
- font-weight: 700;
483
+ font-weight: 500;
496
484
  }
497
485
 
498
486
  .df-review-sitemap-header button {
@@ -506,7 +494,7 @@ function ensureReviewShellStyle() {
506
494
  background: var(--df-review-control);
507
495
  color: var(--df-review-text);
508
496
  font-size: var(--df-review-font-size-md);
509
- font-weight: 800;
497
+ font-weight: 500;
510
498
  }
511
499
 
512
500
  .df-review-sitemap-header button:hover {
@@ -534,15 +522,18 @@ function ensureReviewShellStyle() {
534
522
  background: var(--df-review-control);
535
523
  box-shadow: var(--df-review-shadow-control);
536
524
  font-size: var(--df-review-font-size-xs);
537
- font-weight: 850;
525
+ font-weight: 600;
538
526
  }
539
527
 
540
528
  .df-review-sitemap-list {
541
529
  --df-review-sitemap-grid-template: minmax(190px, 1fr) 74px 78px 64px minmax(108px, 160px);
530
+ position: relative;
542
531
  display: grid;
543
532
  align-content: start;
544
533
  min-height: 0;
545
- overflow: auto;
534
+ overflow-x: hidden;
535
+ overflow-y: auto;
536
+ overscroll-behavior: contain;
546
537
  padding: 8px;
547
538
  }
548
539
 
@@ -557,14 +548,17 @@ function ensureReviewShellStyle() {
557
548
  .df-review-sitemap-table-head {
558
549
  position: sticky;
559
550
  top: 0;
560
- z-index: 1;
551
+ z-index: 3;
561
552
  min-height: 32px;
562
553
  border-bottom: 1px solid var(--df-review-line);
563
554
  padding: 0 10px;
564
- background: var(--df-review-surface);
555
+ background: var(--df-review-panel);
556
+ box-shadow:
557
+ 0 -8px 0 0 var(--df-review-panel),
558
+ 0 1px 0 var(--df-review-line);
565
559
  color: var(--df-review-muted);
566
560
  font-size: var(--df-review-font-size-xs);
567
- font-weight: 720;
561
+ font-weight: 500;
568
562
  letter-spacing: 0.03em;
569
563
  text-transform: uppercase;
570
564
  }
@@ -632,10 +626,13 @@ function ensureReviewShellStyle() {
632
626
  .df-review-sitemap-row.is-summary {
633
627
  position: sticky;
634
628
  bottom: 0;
635
- z-index: 1;
629
+ z-index: 3;
636
630
  border-top: 1px solid var(--df-review-line);
637
631
  border-bottom: 0;
638
- background: var(--df-review-surface);
632
+ background: var(--df-review-panel);
633
+ box-shadow:
634
+ 0 8px 0 0 var(--df-review-panel),
635
+ 0 -1px 0 var(--df-review-line);
639
636
  }
640
637
 
641
638
  .df-review-sitemap-row.is-folder {
@@ -659,7 +656,7 @@ function ensureReviewShellStyle() {
659
656
  overflow-wrap: anywhere;
660
657
  color: var(--df-review-text);
661
658
  font-size: var(--df-review-font-size-md);
662
- font-weight: 650;
659
+ font-weight: 400;
663
660
  line-height: 1.35;
664
661
  }
665
662
 
@@ -671,7 +668,7 @@ function ensureReviewShellStyle() {
671
668
  flex: 0 0 auto;
672
669
  color: var(--df-review-muted);
673
670
  font-family: var(--df-review-font-mono);
674
- font-weight: 500;
671
+ font-weight: 400;
675
672
  white-space: pre;
676
673
  }
677
674
 
@@ -686,14 +683,14 @@ function ensureReviewShellStyle() {
686
683
  color: var(--df-review-muted);
687
684
  font-size: var(--df-review-font-size-sm);
688
685
  font-variant-numeric: tabular-nums;
689
- font-weight: 650;
686
+ font-weight: 400;
690
687
  line-height: 1;
691
688
  text-align: right;
692
689
  }
693
690
 
694
691
  .df-review-sitemap-cell.is-total {
695
692
  color: var(--df-review-accent);
696
- font-weight: 760;
693
+ font-weight: 500;
697
694
  }
698
695
 
699
696
  .df-review-sitemap-cell.is-total strong {
@@ -726,7 +723,7 @@ function ensureReviewShellStyle() {
726
723
  background: var(--df-review-chip-bg);
727
724
  color: var(--df-review-text);
728
725
  font-size: var(--df-review-font-size-xs);
729
- font-weight: 900;
726
+ font-weight: 600;
730
727
  line-height: 1.1;
731
728
  white-space: nowrap;
732
729
  }
@@ -798,12 +795,13 @@ function ensureReviewShellStyle() {
798
795
  .df-review-settings-header strong {
799
796
  color: var(--df-review-text);
800
797
  font-size: var(--df-review-font-size-lg);
798
+ font-weight: 600;
801
799
  }
802
800
 
803
801
  .df-review-settings-header span {
804
802
  color: var(--df-review-muted);
805
803
  font-size: var(--df-review-font-size-xs);
806
- font-weight: 800;
804
+ font-weight: 500;
807
805
  }
808
806
 
809
807
  .df-review-settings-header button {
@@ -813,7 +811,7 @@ function ensureReviewShellStyle() {
813
811
  min-width: 34px;
814
812
  padding: 0;
815
813
  font-size: var(--df-review-font-size-md);
816
- font-weight: 800;
814
+ font-weight: 500;
817
815
  }
818
816
 
819
817
  .df-review-settings-body {
@@ -839,7 +837,7 @@ function ensureReviewShellStyle() {
839
837
  .df-review-settings-label-row label {
840
838
  color: var(--df-review-muted);
841
839
  font-size: var(--df-review-font-size-sm);
842
- font-weight: 800;
840
+ font-weight: 500;
843
841
  }
844
842
 
845
843
  .df-review-settings-theme-options {
@@ -851,17 +849,32 @@ function ensureReviewShellStyle() {
851
849
  }
852
850
 
853
851
  .df-review-settings-theme-option {
852
+ display: inline-flex;
853
+ align-items: center;
854
+ justify-content: center;
855
+ gap: 7px;
854
856
  min-height: 30px;
855
857
  border: 1px solid var(--df-review-line);
856
858
  border-radius: var(--df-review-radius-sm);
857
- padding: 0 10px;
859
+ padding: 0 11px;
858
860
  color: var(--df-review-muted);
859
861
  background: var(--df-review-control);
860
862
  box-shadow: var(--df-review-shadow-control);
861
863
  font-size: var(--df-review-font-size-sm);
862
- font-weight: 800;
864
+ font-weight: 500;
863
865
  }
864
866
 
867
+ .df-review-settings-theme-option svg {
868
+ width: 15px;
869
+ height: 15px;
870
+ flex: 0 0 auto;
871
+ stroke-width: 2.2;
872
+ }
873
+
874
+ .df-review-settings-theme-option span {
875
+ color: inherit;
876
+ }
877
+
865
878
  .df-review-settings-theme-option:hover {
866
879
  border-color: var(--df-review-accent);
867
880
  background: var(--df-review-control-hover);
@@ -1006,7 +1019,7 @@ function ensureReviewShellStyle() {
1006
1019
  background: var(--df-review-chip-bg);
1007
1020
  color: var(--df-review-muted);
1008
1021
  font-size: var(--df-review-font-size-xs);
1009
- font-weight: 700;
1022
+ font-weight: 500;
1010
1023
  line-height: 1.55;
1011
1024
  }
1012
1025
 
@@ -1022,7 +1035,7 @@ function ensureReviewShellStyle() {
1022
1035
  margin: 0;
1023
1036
  color: var(--df-review-accent);
1024
1037
  font-size: var(--df-review-font-size-sm);
1025
- font-weight: 800;
1038
+ font-weight: 500;
1026
1039
  }
1027
1040
 
1028
1041
  .df-review-settings-actions {
@@ -1088,7 +1101,7 @@ function ensureReviewShellStyle() {
1088
1101
  margin: 0;
1089
1102
  color: var(--df-review-danger);
1090
1103
  font-size: var(--df-review-font-size-sm);
1091
- font-weight: 800;
1104
+ font-weight: 500;
1092
1105
  }
1093
1106
 
1094
1107
  .df-review-edit-actions {
@@ -1141,7 +1154,7 @@ function ensureReviewShellStyle() {
1141
1154
  box-shadow: var(--df-review-shadow-modal);
1142
1155
  color: var(--df-review-text);
1143
1156
  font-size: var(--df-review-font-size-sm);
1144
- font-weight: 800;
1157
+ font-weight: 500;
1145
1158
  line-height: 1.25;
1146
1159
  pointer-events: none;
1147
1160
  }
@@ -1165,13 +1178,14 @@ function ensureReviewShellStyle() {
1165
1178
  .df-review-prompt-header strong {
1166
1179
  color: var(--df-review-text);
1167
1180
  font-size: var(--df-review-font-size-lg);
1181
+ font-weight: 600;
1168
1182
  }
1169
1183
 
1170
1184
  .df-review-prompt-header span {
1171
1185
  overflow: hidden;
1172
1186
  color: var(--df-review-muted);
1173
1187
  font-size: var(--df-review-font-size-xs);
1174
- font-weight: 800;
1188
+ font-weight: 500;
1175
1189
  text-overflow: ellipsis;
1176
1190
  white-space: nowrap;
1177
1191
  }
@@ -1183,7 +1197,7 @@ function ensureReviewShellStyle() {
1183
1197
  min-width: 34px;
1184
1198
  padding: 0;
1185
1199
  font-size: var(--df-review-font-size-md);
1186
- font-weight: 800;
1200
+ font-weight: 500;
1187
1201
  }
1188
1202
 
1189
1203
  .df-review-prompt-body {
@@ -1217,13 +1231,13 @@ function ensureReviewShellStyle() {
1217
1231
  .df-review-prompt-block-header strong {
1218
1232
  color: var(--df-review-text);
1219
1233
  font-size: var(--df-review-font-size-sm);
1220
- font-weight: 900;
1234
+ font-weight: 600;
1221
1235
  }
1222
1236
 
1223
1237
  .df-review-prompt-block-header span {
1224
1238
  color: var(--df-review-muted);
1225
1239
  font-size: var(--df-review-font-size-xs);
1226
- font-weight: 800;
1240
+ font-weight: 500;
1227
1241
  }
1228
1242
 
1229
1243
  .df-review-prompt-block-header button {
@@ -1262,7 +1276,7 @@ function ensureReviewShellStyle() {
1262
1276
  color: var(--df-review-text);
1263
1277
  font-family: var(--df-review-font-mono);
1264
1278
  font-size: var(--df-review-font-size-xs);
1265
- font-weight: 600;
1279
+ font-weight: 400;
1266
1280
  line-height: 1.5;
1267
1281
  white-space: pre;
1268
1282
  }
@@ -1281,13 +1295,13 @@ function ensureReviewShellStyle() {
1281
1295
  .df-review-prompt-section-header strong {
1282
1296
  color: var(--df-review-text);
1283
1297
  font-size: var(--df-review-font-size-md);
1284
- font-weight: 900;
1298
+ font-weight: 600;
1285
1299
  }
1286
1300
 
1287
1301
  .df-review-prompt-section-header span {
1288
1302
  color: var(--df-review-muted);
1289
1303
  font-size: var(--df-review-font-size-xs);
1290
- font-weight: 800;
1304
+ font-weight: 500;
1291
1305
  }
1292
1306
 
1293
1307
  .df-review-prompt-about {
@@ -1316,14 +1330,14 @@ function ensureReviewShellStyle() {
1316
1330
  .df-review-prompt-about-grid strong {
1317
1331
  color: var(--df-review-text);
1318
1332
  font-size: var(--df-review-font-size-sm);
1319
- font-weight: 900;
1333
+ font-weight: 600;
1320
1334
  }
1321
1335
 
1322
1336
  .df-review-prompt-about-grid p {
1323
1337
  margin: 0;
1324
1338
  color: var(--df-review-muted);
1325
1339
  font-size: var(--df-review-font-size-sm);
1326
- font-weight: 700;
1340
+ font-weight: 500;
1327
1341
  line-height: 1.55;
1328
1342
  }
1329
1343
 
@@ -1353,8 +1367,9 @@ function ensureReviewShellStyle() {
1353
1367
  display: flex;
1354
1368
  align-items: center;
1355
1369
  gap: var(--df-review-space-1-5);
1356
- min-height: calc(var(--df-review-control-height-lg) + 6px);
1357
- padding: 3px;
1370
+ height: var(--df-review-control-height-md);
1371
+ min-height: var(--df-review-control-height-md);
1372
+ padding: 0;
1358
1373
  border: 1px solid var(--df-review-line-soft);
1359
1374
  border-radius: var(--df-review-radius-md);
1360
1375
  background: var(--df-review-card);
@@ -1366,28 +1381,50 @@ function ensureReviewShellStyle() {
1366
1381
  align-items: center;
1367
1382
  color: var(--df-review-line);
1368
1383
  font-size: var(--df-review-font-size-2xl);
1369
- font-weight: 700;
1384
+ font-weight: 500;
1370
1385
  line-height: 1;
1371
1386
  user-select: none;
1372
1387
  }
1373
1388
 
1374
- .df-review-active-size {
1389
+ .df-review-active-size {
1375
1390
  flex: 0 0 auto;
1376
1391
  display: inline-flex;
1377
1392
  align-items: center;
1378
- min-height: var(--df-review-control-height-lg);
1393
+ min-height: var(--df-review-control-height-md);
1379
1394
  color: var(--df-review-muted);
1380
1395
  font-size: var(--df-review-font-size-sm);
1381
1396
  font-variant-numeric: tabular-nums;
1382
- font-weight: 800;
1397
+ font-weight: 500;
1383
1398
  line-height: 1;
1384
1399
  }
1385
1400
 
1401
+ .df-review-preset-select {
1402
+ -webkit-appearance: none;
1403
+ appearance: none;
1404
+ display: none;
1405
+ height: var(--df-review-control-height-md);
1406
+ min-height: var(--df-review-control-height-md);
1407
+ min-width: 154px;
1408
+ border: 1px solid var(--df-review-line-soft);
1409
+ border-radius: var(--df-review-radius-md);
1410
+ padding: 0 42px 0 14px;
1411
+ color: var(--df-review-text);
1412
+ background-color: var(--df-review-control);
1413
+ background-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m6 9 6 6 6-6' stroke='%23d7deea' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
1414
+ background-position: right 14px center;
1415
+ background-repeat: no-repeat;
1416
+ background-size: 18px 18px;
1417
+ box-shadow: var(--df-review-shadow-control);
1418
+ font-size: var(--df-review-font-size-sm);
1419
+ font-weight: 500;
1420
+ }
1421
+
1386
1422
  .df-review-presets button {
1387
1423
  display: inline-flex;
1388
1424
  align-items: center;
1389
1425
  gap: 7px;
1390
- min-height: var(--df-review-control-height-lg);
1426
+ height: var(--df-review-control-height-md);
1427
+ min-height: var(--df-review-control-height-md);
1391
1428
  padding: 0 11px 0 9px;
1392
1429
  border-color: transparent;
1393
1430
  background: transparent;
@@ -1412,7 +1449,7 @@ function ensureReviewShellStyle() {
1412
1449
  background: var(--df-review-line-soft);
1413
1450
  color: var(--df-review-muted);
1414
1451
  font-size: var(--df-review-font-size-2xs);
1415
- font-weight: 800;
1452
+ font-weight: 500;
1416
1453
  line-height: 16px;
1417
1454
  text-align: center;
1418
1455
  }
@@ -1438,6 +1475,7 @@ function ensureReviewShellStyle() {
1438
1475
  .df-review-preset-copy strong {
1439
1476
  color: var(--df-review-text);
1440
1477
  font-size: var(--df-review-font-size-sm);
1478
+ font-weight: 500;
1441
1479
  }
1442
1480
 
1443
1481
  .df-review-overlay-button,
@@ -1445,8 +1483,10 @@ function ensureReviewShellStyle() {
1445
1483
  position: relative;
1446
1484
  display: inline-grid;
1447
1485
  place-items: center;
1448
- width: 38px;
1449
- min-width: 38px;
1486
+ width: var(--df-review-control-height-md);
1487
+ min-width: var(--df-review-control-height-md);
1488
+ height: var(--df-review-control-height-md);
1489
+ min-height: var(--df-review-control-height-md);
1450
1490
  padding: 0;
1451
1491
  border-color: transparent;
1452
1492
  background: transparent;
@@ -1454,6 +1494,72 @@ function ensureReviewShellStyle() {
1454
1494
  color: var(--df-review-muted);
1455
1495
  }
1456
1496
 
1497
+ .df-review-overlays-menu {
1498
+ position: relative;
1499
+ display: none;
1500
+ flex: 0 0 auto;
1501
+ }
1502
+
1503
+ .df-review-overlays-menu summary {
1504
+ display: grid;
1505
+ place-items: center;
1506
+ width: var(--df-review-control-height-md);
1507
+ min-width: var(--df-review-control-height-md);
1508
+ height: var(--df-review-control-height-md);
1509
+ border: 1px solid var(--df-review-line-soft);
1510
+ border-radius: var(--df-review-radius-md);
1511
+ padding: 0;
1512
+ color: var(--df-review-muted);
1513
+ background: var(--df-review-card);
1514
+ box-shadow: var(--df-review-shadow-control);
1515
+ cursor: pointer;
1516
+ }
1517
+
1518
+ .df-review-overlays-menu summary::-webkit-details-marker {
1519
+ display: none;
1520
+ }
1521
+
1522
+ .df-review-overlays-menu summary::marker {
1523
+ content: '';
1524
+ }
1525
+
1526
+ .df-review-overlays-menu summary:hover,
1527
+ .df-review-overlays-menu[open] summary {
1528
+ border-color: var(--df-review-accent);
1529
+ background: var(--df-review-control-hover);
1530
+ color: var(--df-review-accent);
1531
+ }
1532
+
1533
+ .df-review-overlays-menu summary svg {
1534
+ width: 20px;
1535
+ height: 20px;
1536
+ fill: none;
1537
+ stroke: currentColor;
1538
+ stroke-linecap: round;
1539
+ stroke-linejoin: round;
1540
+ stroke-width: 1.9;
1541
+ }
1542
+
1543
+ .df-review-overlays-popover {
1544
+ position: absolute;
1545
+ top: calc(100% + 6px);
1546
+ right: 0;
1547
+ z-index: 700;
1548
+ display: none;
1549
+ align-items: center;
1550
+ gap: var(--df-review-space-1-5);
1551
+ min-height: var(--df-review-control-height-md);
1552
+ padding: 0;
1553
+ border: 1px solid var(--df-review-line-soft);
1554
+ border-radius: var(--df-review-radius-md);
1555
+ background: var(--df-review-card);
1556
+ box-shadow: var(--df-review-shadow-modal);
1557
+ }
1558
+
1559
+ .df-review-overlays-menu[open] .df-review-overlays-popover {
1560
+ display: flex;
1561
+ }
1562
+
1457
1563
  .df-review-overlay-button svg,
1458
1564
  .df-review-mode-button svg {
1459
1565
  width: 18px;
@@ -1489,33 +1595,46 @@ function ensureReviewShellStyle() {
1489
1595
 
1490
1596
  @container (max-width: 1040px) {
1491
1597
  .df-review-tools {
1492
- flex-direction: column;
1493
- align-items: stretch;
1494
- justify-content: flex-start;
1598
+ flex-direction: row;
1599
+ align-items: center;
1600
+ justify-content: space-between;
1495
1601
  }
1496
1602
 
1497
1603
  .df-review-tool-controls {
1498
- width: 100%;
1604
+ flex: 1 1 auto;
1605
+ width: auto;
1606
+ min-width: 0;
1607
+ flex-wrap: nowrap;
1499
1608
  }
1500
1609
 
1501
1610
  .df-review-presets {
1502
- flex-wrap: wrap;
1611
+ display: none;
1612
+ }
1613
+
1614
+ .df-review-preset-select {
1615
+ display: block;
1616
+ flex: 0 1 190px;
1617
+ width: clamp(154px, 26vw, 190px);
1503
1618
  }
1504
1619
 
1505
1620
  .df-review-overlays {
1506
- width: 100%;
1507
- justify-content: flex-start;
1621
+ display: none;
1508
1622
  }
1623
+
1624
+ .df-review-overlays-menu {
1625
+ display: block;
1626
+ }
1509
1627
  }
1510
1628
 
1511
1629
  .df-review-side-rail {
1512
1630
  grid-column: 3;
1513
1631
  grid-row: 1 / span 3;
1514
1632
  position: relative;
1515
- z-index: 600;
1633
+ z-index: 640;
1516
1634
  display: flex;
1635
+ flex-direction: column;
1517
1636
  align-items: stretch;
1518
- justify-content: center;
1637
+ justify-content: flex-start;
1519
1638
  min-width: 0;
1520
1639
  min-height: 0;
1521
1640
  border-left: 1px solid var(--df-review-line);
@@ -1523,49 +1642,166 @@ function ensureReviewShellStyle() {
1523
1642
  }
1524
1643
 
1525
1644
  .df-review-side-toggle {
1645
+ position: relative;
1646
+ flex: 0 0 58px;
1526
1647
  display: grid;
1527
- grid-template-rows: 28px auto;
1528
- align-items: start;
1529
- justify-items: center;
1530
- gap: 8px;
1648
+ place-items: center;
1531
1649
  width: 100%;
1532
- min-height: 100%;
1650
+ min-height: 58px;
1533
1651
  border: 0;
1534
1652
  border-radius: 0;
1535
- padding: 10px 0;
1653
+ padding: 0;
1536
1654
  background: transparent;
1537
1655
  color: var(--df-review-muted);
1656
+ transition: color 140ms ease, background 140ms ease;
1538
1657
  }
1539
1658
 
1540
1659
  .df-review-side-toggle:hover {
1541
- background: var(--df-review-accent-soft);
1542
1660
  color: var(--df-review-text);
1543
1661
  }
1544
1662
 
1663
+ .df-review-side-toggle.is-active {
1664
+ color: var(--df-review-accent);
1665
+ }
1666
+
1667
+ .df-review-side-toggle.is-active::after {
1668
+ position: absolute;
1669
+ top: 12px;
1670
+ right: 0;
1671
+ bottom: 12px;
1672
+ width: 3px;
1673
+ border-radius: 3px 0 0 3px;
1674
+ background: var(--df-review-accent);
1675
+ content: '';
1676
+ }
1677
+
1678
+ .df-review-side-toggle:focus-visible {
1679
+ outline: 0;
1680
+ color: var(--df-review-text);
1681
+ }
1682
+
1683
+ .df-review-side-toggle:focus-visible span {
1684
+ border-radius: 6px;
1685
+ outline: 2px solid var(--df-review-focus-ring);
1686
+ outline-offset: 3px;
1687
+ }
1688
+
1545
1689
  .df-review-side-toggle span {
1546
1690
  display: grid;
1547
1691
  place-items: center;
1548
- width: 20px;
1549
- height: 24px;
1692
+ width: 30px;
1693
+ height: 30px;
1550
1694
  line-height: 1;
1551
1695
  }
1552
1696
 
1553
1697
  .df-review-side-toggle svg {
1554
- width: 18px;
1555
- height: 18px;
1698
+ width: 30px;
1699
+ height: 30px;
1556
1700
  fill: none;
1557
1701
  stroke: currentColor;
1558
1702
  stroke-linecap: round;
1559
- stroke-width: 2;
1703
+ stroke-width: 1.55;
1560
1704
  }
1561
1705
 
1562
- .df-review-side-toggle strong {
1563
- writing-mode: vertical-rl;
1564
- text-orientation: mixed;
1565
- color: inherit;
1566
- font-size: var(--df-review-font-size-2xs);
1567
- font-weight: 900;
1568
- letter-spacing: 0.08em;
1706
+ .df-review-presence-overlay {
1707
+ position: relative;
1708
+ margin-top: auto;
1709
+ }
1710
+
1711
+ .df-review-presence-button {
1712
+ position: relative;
1713
+ display: grid;
1714
+ place-items: center;
1715
+ width: 100%;
1716
+ min-height: 58px;
1717
+ border: 0;
1718
+ border-radius: 0;
1719
+ padding: 0;
1720
+ color: var(--df-review-muted);
1721
+ background: transparent;
1722
+ }
1723
+
1724
+ .df-review-presence-button:hover,
1725
+ .df-review-presence-button[aria-expanded="true"] {
1726
+ color: var(--df-review-text);
1727
+ }
1728
+
1729
+ .df-review-presence-button:focus-visible {
1730
+ outline: 0;
1731
+ }
1732
+
1733
+ .df-review-presence-button:focus-visible svg {
1734
+ border-radius: 6px;
1735
+ outline: 2px solid var(--df-review-focus-ring);
1736
+ outline-offset: 3px;
1737
+ }
1738
+
1739
+ .df-review-presence-button svg {
1740
+ width: 30px;
1741
+ height: 30px;
1742
+ fill: none;
1743
+ stroke: currentColor;
1744
+ stroke-linecap: round;
1745
+ stroke-linejoin: round;
1746
+ stroke-width: 1.55;
1747
+ }
1748
+
1749
+ .df-review-presence-badge {
1750
+ position: absolute;
1751
+ top: 10px;
1752
+ right: 7px;
1753
+ display: grid;
1754
+ place-items: center;
1755
+ min-width: 17px;
1756
+ height: 17px;
1757
+ border: 1px solid var(--df-review-side-rail);
1758
+ border-radius: var(--df-review-radius-pill);
1759
+ padding: 0 4px;
1760
+ color: #17202c;
1761
+ background: var(--df-review-accent);
1762
+ font-size: var(--df-review-font-size-3xs);
1763
+ font-weight: 600;
1764
+ line-height: 1;
1765
+ pointer-events: none;
1766
+ }
1767
+
1768
+ .df-review-presence-list {
1769
+ position: absolute;
1770
+ right: calc(100% + 8px);
1771
+ bottom: 8px;
1772
+ display: grid;
1773
+ gap: 5px;
1774
+ min-width: 132px;
1775
+ max-width: 220px;
1776
+ border: 1px solid var(--df-review-line);
1777
+ border-radius: var(--df-review-radius-md);
1778
+ padding: 8px;
1779
+ background: var(--df-review-panel);
1780
+ box-shadow: var(--df-review-shadow-modal);
1781
+ }
1782
+
1783
+ .df-review-presence-chip {
1784
+ --df-review-presence-color: var(--df-review-accent);
1785
+ display: flex;
1786
+ align-items: center;
1787
+ min-width: 0;
1788
+ min-height: 24px;
1789
+ border-left: 2px solid var(--df-review-presence-color);
1790
+ border-radius: var(--df-review-radius-sm);
1791
+ padding: 0 8px;
1792
+ color: var(--df-review-text);
1793
+ background: var(--df-review-control);
1794
+ font-size: var(--df-review-font-size-xs);
1795
+ font-weight: 500;
1796
+ line-height: 1.1;
1797
+ overflow: hidden;
1798
+ text-overflow: ellipsis;
1799
+ white-space: nowrap;
1800
+ }
1801
+
1802
+ .df-review-presence-chip.is-self {
1803
+ color: var(--df-review-accent);
1804
+ background: var(--df-review-accent-soft);
1569
1805
  }
1570
1806
 
1571
1807
  .df-review-qa-panel {
@@ -1583,11 +1819,34 @@ function ensureReviewShellStyle() {
1583
1819
  linear-gradient(180deg, var(--df-review-panel), var(--df-review-bg));
1584
1820
  }
1585
1821
 
1586
- .df-review-shell:not(.is-list-visible) .df-review-qa-panel {
1822
+ .df-review-shell:not(.is-list-visible) .df-review-qa-panel,
1823
+ .df-review-qa-panel[aria-hidden="true"] {
1587
1824
  visibility: hidden;
1588
1825
  border-left: 0;
1826
+ pointer-events: none;
1589
1827
  }
1590
1828
 
1829
+ .df-review-source-tree-panel {
1830
+ grid-column: 2;
1831
+ grid-row: 1 / span 3;
1832
+ position: relative;
1833
+ z-index: 600;
1834
+ display: grid;
1835
+ min-width: 0;
1836
+ min-height: 0;
1837
+ overflow: hidden;
1838
+ border-left: 1px solid var(--df-review-line-soft);
1839
+ background:
1840
+ linear-gradient(180deg, var(--df-review-panel), var(--df-review-bg));
1841
+ }
1842
+
1843
+ .df-review-shell:not(.is-list-visible) .df-review-source-tree-panel,
1844
+ .df-review-source-tree-panel[aria-hidden="true"] {
1845
+ visibility: hidden;
1846
+ border-left: 0;
1847
+ pointer-events: none;
1848
+ }
1849
+
1591
1850
  .df-review-panel-body {
1592
1851
  display: grid;
1593
1852
  min-width: 0;
@@ -1604,14 +1863,16 @@ function ensureReviewShellStyle() {
1604
1863
 
1605
1864
  .df-review-list-header {
1606
1865
  display: grid;
1607
- grid-template-rows: auto auto;
1866
+ grid-template-rows:
1867
+ var(--df-review-control-height-md)
1868
+ var(--df-review-control-height-md);
1608
1869
  gap: var(--df-review-space-2);
1609
1870
  padding: var(--df-review-space-3) var(--df-review-frame-gutter-x);
1610
1871
  border-bottom: 1px solid var(--df-review-line-soft);
1611
1872
  background: var(--df-review-card);
1612
1873
  color: var(--df-review-muted);
1613
1874
  font-size: var(--df-review-font-size-sm);
1614
- font-weight: 800;
1875
+ font-weight: 500;
1615
1876
  }
1616
1877
 
1617
1878
  .df-review-list-toolbar {
@@ -1619,122 +1880,59 @@ function ensureReviewShellStyle() {
1619
1880
  grid-template-columns: minmax(0, 1fr) auto;
1620
1881
  align-items: center;
1621
1882
  gap: 8px;
1883
+ min-height: var(--df-review-control-height-md);
1622
1884
  min-width: 0;
1623
1885
  }
1624
1886
 
1625
1887
  .df-review-list-title {
1626
- display: flex;
1888
+ display: grid;
1889
+ grid-template-columns: minmax(0, 1fr) auto;
1627
1890
  align-items: center;
1628
1891
  gap: 8px;
1892
+ min-height: var(--df-review-control-height-md);
1629
1893
  min-width: 0;
1630
1894
  }
1631
1895
 
1632
- .df-review-list-title span {
1896
+ .df-review-list-meta {
1897
+ display: flex;
1898
+ align-items: center;
1899
+ gap: 8px;
1900
+ min-width: 0;
1901
+ }
1902
+
1903
+ .df-review-list-meta span {
1633
1904
  min-width: 0;
1634
1905
  overflow: hidden;
1635
1906
  text-overflow: ellipsis;
1636
1907
  white-space: nowrap;
1637
1908
  }
1638
1909
 
1639
- .df-review-list-title strong {
1640
- flex: 0 0 auto;
1641
- color: var(--df-review-muted);
1642
- font-size: var(--df-review-font-size-xs);
1643
- font-variant-numeric: tabular-nums;
1644
- }
1910
+ .df-review-list-meta strong {
1911
+ flex: 0 0 auto;
1912
+ color: var(--df-review-muted);
1913
+ font-size: var(--df-review-font-size-xs);
1914
+ font-variant-numeric: tabular-nums;
1915
+ font-weight: 500;
1916
+ }
1645
1917
 
1646
- .df-review-presence-overlay {
1918
+ .df-review-list-controls {
1647
1919
  display: flex;
1648
1920
  align-items: center;
1649
- flex-wrap: wrap;
1650
- gap: 5px;
1651
- max-width: 100%;
1652
- pointer-events: none;
1921
+ gap: 4px;
1653
1922
  }
1654
1923
 
1655
- .df-review-presence-row .df-review-presence-overlay {
1656
- width: 100%;
1657
- max-width: 1440px;
1658
- margin: 0 auto;
1659
- }
1660
-
1661
- .df-review-presence-chip {
1662
- --df-review-presence-color: var(--df-review-accent);
1663
- display: inline-flex;
1664
- align-items: center;
1665
- min-width: 0;
1666
- max-width: min(220px, calc(100% - 20px));
1667
- min-height: 24px;
1668
- border: 1px solid rgba(15, 23, 42, 0.12);
1669
- border-radius: var(--df-review-radius-pill);
1670
- padding: 0 8px;
1671
- color: #17202c;
1672
- background: rgba(255, 255, 255, 0.92);
1673
- box-shadow: 0 8px 22px rgba(15, 23, 42, 0.14);
1674
- font-size: var(--df-review-font-size-2xs);
1675
- font-weight: 900;
1676
- line-height: 1.1;
1677
- overflow: hidden;
1678
- text-overflow: ellipsis;
1679
- white-space: nowrap;
1680
- backdrop-filter: blur(8px);
1681
- }
1682
-
1683
- .df-review-presence-row .df-review-presence-chip {
1684
- max-width: none;
1685
- }
1686
-
1687
- .df-review-presence-chip.is-self {
1688
- border-color: var(--df-review-presence-color);
1689
- box-shadow:
1690
- inset 0 0 0 1px var(--df-review-presence-color),
1691
- 0 8px 22px rgba(15, 23, 42, 0.14);
1692
- }
1693
-
1694
- .df-review-presence-more {
1695
- display: inline-flex;
1696
- align-items: center;
1697
- justify-content: center;
1698
- min-width: 28px;
1699
- min-height: 24px;
1700
- border: 1px solid rgba(15, 23, 42, 0.12);
1701
- border-radius: var(--df-review-radius-pill);
1702
- padding: 0 8px;
1703
- color: #17202c;
1704
- background: rgba(255, 255, 255, 0.92);
1705
- box-shadow: 0 8px 22px rgba(15, 23, 42, 0.14);
1706
- cursor: pointer;
1707
- font: inherit;
1708
- font-size: var(--df-review-font-size-2xs);
1709
- font-weight: 900;
1710
- line-height: 1;
1711
- pointer-events: auto;
1712
- backdrop-filter: blur(8px);
1713
- }
1714
-
1715
- .df-review-presence-more:hover {
1716
- border-color: rgba(0, 102, 255, 0.42);
1717
- color: #005be8;
1718
- background: rgba(255, 255, 255, 0.98);
1719
- }
1720
-
1721
- .df-review-list-controls {
1722
- display: flex;
1723
- align-items: center;
1724
- gap: 4px;
1725
- }
1726
-
1727
- .df-review-source-select,
1728
- .df-review-status-filter-select {
1729
- min-height: 30px;
1730
- border: 1px solid var(--df-review-line-soft);
1731
- border-radius: var(--df-review-radius-sm);
1732
- padding: 0 24px 0 8px;
1733
- color: var(--df-review-text);
1734
- background: var(--df-review-control);
1735
- box-shadow: var(--df-review-shadow-control);
1736
- font-size: var(--df-review-font-size-xs);
1737
- font-weight: 800;
1924
+ .df-review-source-select,
1925
+ .df-review-status-filter-select {
1926
+ height: var(--df-review-control-height-md);
1927
+ min-height: var(--df-review-control-height-md);
1928
+ border: 1px solid var(--df-review-line-soft);
1929
+ border-radius: var(--df-review-radius-sm);
1930
+ padding: 0 24px 0 8px;
1931
+ color: var(--df-review-text);
1932
+ background: var(--df-review-control);
1933
+ box-shadow: var(--df-review-shadow-control);
1934
+ font-size: var(--df-review-font-size-xs);
1935
+ font-weight: 500;
1738
1936
  }
1739
1937
 
1740
1938
  .df-review-source-select {
@@ -1745,17 +1943,13 @@ function ensureReviewShellStyle() {
1745
1943
  width: 124px;
1746
1944
  }
1747
1945
 
1748
- .df-review-list-title .df-review-status-filter-select {
1749
- margin-left: auto;
1750
- }
1751
-
1752
1946
  .df-review-source-refresh {
1753
1947
  position: relative;
1754
1948
  display: inline-grid;
1755
1949
  place-items: center;
1756
- width: 30px;
1757
- min-width: 30px;
1758
- height: 30px;
1950
+ width: var(--df-review-control-height-md);
1951
+ min-width: var(--df-review-control-height-md);
1952
+ height: var(--df-review-control-height-md);
1759
1953
  border: 1px solid var(--df-review-line-soft);
1760
1954
  border-radius: var(--df-review-radius-sm);
1761
1955
  padding: 0;
@@ -1765,16 +1959,18 @@ function ensureReviewShellStyle() {
1765
1959
  }
1766
1960
 
1767
1961
  .df-review-source-refresh svg {
1768
- width: 14px;
1769
- height: 14px;
1962
+ width: 16px;
1963
+ height: 16px;
1770
1964
  }
1771
1965
 
1772
1966
  .df-review-filter-tabs {
1773
1967
  display: flex;
1774
1968
  align-items: center;
1775
- gap: 2px;
1776
- height: 30px;
1777
- padding: 2px;
1969
+ justify-self: end;
1970
+ gap: var(--df-review-space-1-5);
1971
+ height: var(--df-review-control-height-md);
1972
+ min-height: var(--df-review-control-height-md);
1973
+ padding: 0;
1778
1974
  border: 1px solid var(--df-review-line-soft);
1779
1975
  border-radius: var(--df-review-radius-md);
1780
1976
  background: var(--df-review-card);
@@ -1785,11 +1981,11 @@ function ensureReviewShellStyle() {
1785
1981
  position: relative;
1786
1982
  display: grid;
1787
1983
  place-items: center;
1788
- width: 26px;
1789
- min-width: 26px;
1790
- height: 26px;
1984
+ width: var(--df-review-control-height-md);
1985
+ min-width: var(--df-review-control-height-md);
1986
+ height: var(--df-review-control-height-md);
1791
1987
  border: 0;
1792
- border-radius: 5px;
1988
+ border-radius: var(--df-review-radius-sm);
1793
1989
  background: transparent;
1794
1990
  color: var(--df-review-subtle);
1795
1991
  }
@@ -1822,19 +2018,19 @@ function ensureReviewShellStyle() {
1822
2018
  }
1823
2019
 
1824
2020
  .df-review-filter-icon svg {
1825
- width: 15px;
1826
- height: 15px;
2021
+ width: 18px;
2022
+ height: 18px;
1827
2023
  fill: none;
1828
2024
  stroke: currentColor;
1829
2025
  stroke-linecap: round;
1830
2026
  stroke-linejoin: round;
1831
- stroke-width: 2;
2027
+ stroke-width: 1.9;
1832
2028
  }
1833
2029
 
1834
2030
  .df-review-filter-count {
1835
2031
  color: currentColor;
1836
2032
  font-size: var(--df-review-font-size-3xs);
1837
- font-weight: 900;
2033
+ font-weight: 600;
1838
2034
  font-variant-numeric: tabular-nums;
1839
2035
  line-height: 1;
1840
2036
  }
@@ -1915,6 +2111,7 @@ function ensureReviewShellStyle() {
1915
2111
  .df-review-item-main strong {
1916
2112
  overflow-wrap: anywhere;
1917
2113
  font-size: var(--df-review-font-size-md);
2114
+ font-weight: 500;
1918
2115
  line-height: 1.35;
1919
2116
  }
1920
2117
 
@@ -1962,7 +2159,7 @@ function ensureReviewShellStyle() {
1962
2159
  border-radius: var(--df-review-radius-pill);
1963
2160
  padding: 0 7px;
1964
2161
  font-size: var(--df-review-font-size-2xs);
1965
- font-weight: 900;
2162
+ font-weight: 600;
1966
2163
  line-height: 1;
1967
2164
  text-transform: uppercase;
1968
2165
  }
@@ -2029,7 +2226,7 @@ function ensureReviewShellStyle() {
2029
2226
  border-radius: var(--df-review-radius-sm);
2030
2227
  padding: 0 11px;
2031
2228
  font-size: var(--df-review-font-size-xs);
2032
- font-weight: 750;
2229
+ font-weight: 500;
2033
2230
  text-transform: none;
2034
2231
  }
2035
2232
 
@@ -2042,7 +2239,7 @@ function ensureReviewShellStyle() {
2042
2239
  cursor: pointer;
2043
2240
  outline: 0;
2044
2241
  font-size: var(--df-review-font-size-xs);
2045
- font-weight: 750;
2242
+ font-weight: 500;
2046
2243
  text-transform: none;
2047
2244
  }
2048
2245
 
@@ -2267,7 +2464,7 @@ function ensureReviewShellStyle() {
2267
2464
 
2268
2465
  .df-review-item-submit-button span {
2269
2466
  font-size: var(--df-review-font-size-xs);
2270
- font-weight: 750;
2467
+ font-weight: 500;
2271
2468
  line-height: 1;
2272
2469
  text-transform: none;
2273
2470
  white-space: nowrap;
@@ -2439,6 +2636,9 @@ function ensureReviewShellStyle() {
2439
2636
  --df-review-source-popover-text: #edf3fb;
2440
2637
  --df-review-source-popover-muted: rgba(237, 243, 251, 0.68);
2441
2638
  --df-review-source-popover-subtle: rgba(237, 243, 251, 0.5);
2639
+ --df-review-source-popover-data: #f3b75f;
2640
+ --df-review-source-popover-data-muted: rgba(243, 183, 95, 0.78);
2641
+ --df-review-source-popover-data-subtle: rgba(243, 183, 95, 0.62);
2442
2642
  --df-review-source-popover-hover: rgba(124, 199, 255, 0.14);
2443
2643
  position: fixed;
2444
2644
  z-index: 890;
@@ -2450,7 +2650,7 @@ function ensureReviewShellStyle() {
2450
2650
  overflow: hidden;
2451
2651
  border: 1px solid var(--df-review-source-popover-line);
2452
2652
  border-radius: var(--df-review-radius-md);
2453
- padding: 8px 6px 6px;
2653
+ padding: 6px;
2454
2654
  color: var(--df-review-source-popover-text);
2455
2655
  color-scheme: dark;
2456
2656
  background: rgba(19, 24, 33, 0.96);
@@ -2476,7 +2676,7 @@ function ensureReviewShellStyle() {
2476
2676
  color: var(--df-review-source-popover-subtle);
2477
2677
  background: transparent;
2478
2678
  font-size: 16px;
2479
- font-weight: 800;
2679
+ font-weight: 500;
2480
2680
  line-height: 1;
2481
2681
  }
2482
2682
 
@@ -2489,29 +2689,37 @@ function ensureReviewShellStyle() {
2489
2689
  .df-review-source-candidate-list {
2490
2690
  display: grid;
2491
2691
  gap: 0;
2692
+ max-height: min(220px, calc(100vh - 96px));
2492
2693
  min-height: 0;
2493
- overflow: auto;
2694
+ overflow-x: hidden;
2695
+ overflow-y: auto;
2696
+ padding-right: 2px;
2697
+ scrollbar-gutter: stable;
2494
2698
  }
2495
2699
 
2496
2700
  .df-review-source-candidate {
2497
2701
  display: grid;
2498
2702
  width: 100%;
2499
- min-height: 54px;
2500
2703
  border: 0;
2704
+ border-top: 1px solid transparent;
2501
2705
  border-radius: var(--df-review-radius-sm);
2502
- padding: 6px 30px 6px 8px;
2706
+ padding: 9px 30px 9px 8px;
2503
2707
  color: var(--df-review-source-popover-text);
2504
2708
  background: transparent;
2505
2709
  text-align: left;
2506
2710
  }
2507
2711
 
2712
+ .df-review-source-candidate + .df-review-source-candidate {
2713
+ border-top-color: var(--df-review-source-popover-line);
2714
+ }
2715
+
2508
2716
  .df-review-source-candidate:hover {
2509
2717
  background: var(--df-review-source-popover-hover);
2510
2718
  }
2511
2719
 
2512
2720
  .df-review-source-candidate-main {
2513
2721
  display: grid;
2514
- gap: 2px;
2722
+ gap: 1px;
2515
2723
  min-width: 0;
2516
2724
  }
2517
2725
 
@@ -2524,7 +2732,8 @@ function ensureReviewShellStyle() {
2524
2732
 
2525
2733
  .df-review-source-candidate-main strong {
2526
2734
  font-size: var(--df-review-font-size-xs);
2527
- font-weight: 900;
2735
+ font-weight: 600;
2736
+ line-height: 1.2;
2528
2737
  }
2529
2738
 
2530
2739
  .df-review-source-candidate-main span {
@@ -2532,7 +2741,7 @@ function ensureReviewShellStyle() {
2532
2741
  color: var(--df-review-source-popover-muted);
2533
2742
  font-family: var(--df-review-font-mono);
2534
2743
  font-size: var(--df-review-font-size-2xs);
2535
- line-height: 1.25;
2744
+ line-height: 1.24;
2536
2745
  white-space: normal;
2537
2746
  }
2538
2747
 
@@ -2540,161 +2749,490 @@ function ensureReviewShellStyle() {
2540
2749
  color: var(--df-review-source-popover-subtle);
2541
2750
  font-family: var(--df-review-font-mono);
2542
2751
  font-size: var(--df-review-font-size-2xs);
2752
+ line-height: 1.2;
2543
2753
  }
2544
2754
 
2545
- .df-review-device-frame {
2546
- position: relative;
2547
- box-sizing: border-box;
2548
- flex: 0 0 auto;
2549
- }
2550
-
2551
- .df-review-ruler-corner {
2552
- position: absolute;
2553
- left: -26px;
2554
- top: -26px;
2555
- width: 26px;
2556
- height: 26px;
2557
- z-index: 6;
2558
- border-right: 1px solid var(--df-review-line-soft);
2559
- border-bottom: 1px solid var(--df-review-line-soft);
2560
- background: var(--df-review-color-ruler-surface);
2755
+ .df-review-source-candidate.is-data .df-review-source-candidate-main strong {
2756
+ color: var(--df-review-source-popover-data);
2561
2757
  }
2562
2758
 
2563
- .df-review-ruler-gutter {
2564
- position: absolute;
2565
- z-index: 6;
2566
- background: var(--df-review-color-ruler-surface);
2567
- color: var(--df-review-muted);
2568
- user-select: none;
2759
+ .df-review-source-candidate.is-data .df-review-source-candidate-main span {
2760
+ color: var(--df-review-source-popover-data-muted);
2569
2761
  }
2570
2762
 
2571
- .df-review-ruler-gutter.is-x {
2572
- left: 0;
2573
- right: 0;
2574
- top: -26px;
2575
- height: 26px;
2576
- border-bottom: 1px solid var(--df-review-line-soft);
2577
- background-image:
2578
- linear-gradient(
2579
- to right,
2580
- var(--df-review-color-ruler-tick-major) 1px,
2581
- transparent 1px
2582
- ),
2583
- linear-gradient(
2584
- to right,
2585
- var(--df-review-color-ruler-tick-minor) 1px,
2586
- transparent 1px
2587
- );
2588
- background-size:
2589
- calc(var(--df-review-ruler-step-x) * 5) 11px,
2590
- var(--df-review-ruler-step-x) 6px;
2591
- background-position: left bottom;
2592
- background-repeat: repeat-x;
2763
+ .df-review-source-candidate.is-data .df-review-source-candidate-main small {
2764
+ color: var(--df-review-source-popover-data-subtle);
2593
2765
  }
2594
2766
 
2595
- .df-review-ruler-gutter.is-y {
2596
- left: -26px;
2597
- top: 0;
2598
- bottom: 0;
2599
- width: 26px;
2600
- border-right: 1px solid var(--df-review-line-soft);
2601
- background-image:
2602
- linear-gradient(
2603
- to bottom,
2604
- var(--df-review-color-ruler-tick-major) 1px,
2605
- transparent 1px
2606
- ),
2607
- linear-gradient(
2608
- to bottom,
2609
- var(--df-review-color-ruler-tick-minor) 1px,
2610
- transparent 1px
2611
- );
2612
- background-size:
2613
- 11px calc(var(--df-review-ruler-step-y) * 5),
2614
- 6px var(--df-review-ruler-step-y);
2615
- background-position: right top;
2616
- background-repeat: repeat-y;
2767
+ .df-review-section-outline {
2768
+ position: relative;
2769
+ z-index: 1;
2770
+ display: grid;
2771
+ grid-template-rows: auto minmax(0, 1fr);
2772
+ width: 100%;
2773
+ height: 100%;
2774
+ min-width: 0;
2775
+ min-height: 0;
2776
+ border: 0;
2777
+ border-radius: 0;
2778
+ color: var(--df-review-text);
2779
+ background: transparent;
2780
+ box-shadow: none;
2781
+ overflow: hidden;
2617
2782
  }
2618
2783
 
2619
- .df-review-ruler-frame-label {
2620
- position: absolute;
2621
- right: 6px;
2622
- top: 50%;
2623
- transform: translateY(-50%);
2624
- display: flex;
2784
+ .df-review-section-outline-head {
2785
+ display: grid;
2786
+ grid-template-rows:
2787
+ var(--df-review-control-height-md)
2788
+ var(--df-review-control-height-md);
2625
2789
  align-items: center;
2626
- gap: 6px;
2627
- padding: 4px 8px;
2628
- border: 1px solid var(--df-review-color-ruler-popover-border);
2629
- border-radius: var(--df-review-radius-sm);
2630
- background: var(--df-review-color-ruler-label);
2631
- line-height: 1;
2632
- white-space: nowrap;
2633
- box-shadow: 0 6px 18px var(--df-review-color-ruler-popover-shadow);
2790
+ gap: 8px;
2791
+ min-width: 0;
2792
+ padding: var(--df-review-space-3) var(--df-review-frame-gutter-x);
2793
+ border-bottom: 1px solid var(--df-review-line-soft);
2794
+ background: var(--df-review-card);
2795
+ color: var(--df-review-muted);
2634
2796
  }
2635
2797
 
2636
- .df-review-ruler-frame-label strong {
2637
- color: var(--df-review-color-ruler-label-text);
2638
- font-size: var(--df-review-font-size-xs);
2639
- font-weight: 900;
2798
+ .df-review-section-outline-head span {
2799
+ display: grid;
2800
+ gap: 2px;
2801
+ min-width: 0;
2640
2802
  }
2641
2803
 
2642
- .df-review-ruler-frame-label span {
2643
- color: var(--df-review-color-ruler-label-text);
2644
- font-size: var(--df-review-font-size-xs);
2645
- font-weight: 900;
2646
- opacity: 0.78;
2804
+ .df-review-section-outline-head strong {
2805
+ overflow: hidden;
2806
+ color: var(--df-review-text);
2807
+ font-size: var(--df-review-font-size-lg);
2808
+ font-weight: 600;
2809
+ text-overflow: ellipsis;
2810
+ white-space: nowrap;
2647
2811
  }
2648
2812
 
2649
- .df-review-ruler-coord {
2650
- position: absolute;
2651
- z-index: 7;
2652
- padding: 4px 6px;
2653
- border: 1px solid var(--df-review-color-ruler-popover-border);
2654
- border-radius: var(--df-review-radius-xs);
2655
- background: var(--df-review-color-ruler-coord-bg);
2656
- color: var(--df-review-color-ruler-coord-text);
2813
+ .df-review-section-outline-head small {
2814
+ overflow: hidden;
2815
+ color: var(--df-review-muted);
2657
2816
  font-size: var(--df-review-font-size-xs);
2658
- font-weight: 900;
2659
- line-height: 1;
2817
+ font-weight: 500;
2818
+ text-overflow: ellipsis;
2660
2819
  white-space: nowrap;
2661
- pointer-events: none;
2662
- box-shadow: 0 6px 18px var(--df-review-color-ruler-popover-shadow);
2663
2820
  }
2664
2821
 
2665
- .df-review-ruler-coord.is-x {
2666
- top: 50%;
2667
- transform: translate(-50%, -50%);
2822
+ .df-review-section-outline-filter {
2823
+ display: grid;
2824
+ grid-template-columns: 18px minmax(0, 1fr) auto;
2825
+ align-items: center;
2826
+ align-self: stretch;
2827
+ gap: 8px;
2828
+ min-width: 0;
2829
+ height: var(--df-review-control-height-md);
2830
+ min-height: var(--df-review-control-height-md);
2831
+ border: 1px solid var(--df-review-line-soft);
2832
+ border-radius: var(--df-review-radius-md);
2833
+ padding: 0 7px 0 11px;
2834
+ color: var(--df-review-muted);
2835
+ background: var(--df-review-control);
2836
+ box-shadow: var(--df-review-shadow-control);
2668
2837
  }
2669
2838
 
2670
- .df-review-ruler-coord.is-y {
2671
- left: 50%;
2672
- transform: translate(-50%, -50%);
2839
+ .df-review-section-outline-filter:focus-within {
2840
+ border-color: var(--df-review-accent);
2841
+ box-shadow: inset 0 0 0 1px var(--df-review-accent-hover);
2673
2842
  }
2674
2843
 
2675
- .df-review-ruler-overlay {
2676
- position: absolute;
2677
- left: 0;
2678
- top: 0;
2679
- width: inherit;
2680
- height: inherit;
2681
- z-index: 5;
2682
- cursor: crosshair;
2683
- overflow: hidden;
2684
- touch-action: none;
2685
- user-select: none;
2844
+ .df-review-section-outline-filter svg {
2845
+ width: 18px;
2846
+ height: 18px;
2847
+ fill: none;
2848
+ stroke: currentColor;
2849
+ stroke-linecap: round;
2850
+ stroke-linejoin: round;
2851
+ stroke-width: 1.9;
2686
2852
  }
2687
2853
 
2688
- .df-review-ruler-overlay.is-dragging {
2689
- cursor: crosshair;
2854
+ .df-review-section-outline-filter input {
2855
+ width: 100%;
2856
+ min-width: 0;
2857
+ height: 100%;
2858
+ border: 0;
2859
+ padding: 0;
2860
+ color: var(--df-review-text);
2861
+ background: transparent;
2862
+ font-size: var(--df-review-font-size-xs);
2863
+ font-weight: 500;
2864
+ outline: 0;
2865
+ -webkit-appearance: none;
2866
+ appearance: none;
2690
2867
  }
2691
2868
 
2692
- .df-review-ruler-guide {
2693
- position: absolute;
2694
- z-index: 2;
2695
- pointer-events: none;
2696
- background: var(--df-review-color-ruler-guide);
2697
- box-shadow: 0 0 0 1px var(--df-review-color-ruler-measure-shadow);
2869
+ .df-review-section-outline-filter input::placeholder {
2870
+ color: var(--df-review-subtle);
2871
+ }
2872
+
2873
+ .df-review-section-outline-filter input::-webkit-search-cancel-button,
2874
+ .df-review-section-outline-filter input::-webkit-search-decoration {
2875
+ display: none;
2876
+ -webkit-appearance: none;
2877
+ appearance: none;
2878
+ }
2879
+
2880
+ .df-review-section-outline-filter-clear {
2881
+ display: inline-grid;
2882
+ place-items: center;
2883
+ width: var(--df-review-control-height-md);
2884
+ height: var(--df-review-control-height-md);
2885
+ border: 0;
2886
+ border-radius: var(--df-review-radius-sm);
2887
+ padding: 0;
2888
+ color: var(--df-review-muted);
2889
+ background: transparent;
2890
+ cursor: pointer;
2891
+ }
2892
+
2893
+ .df-review-section-outline-filter-clear:hover,
2894
+ .df-review-section-outline-filter-clear:focus-visible {
2895
+ color: var(--df-review-text);
2896
+ background: var(--df-review-accent-soft);
2897
+ outline: 0;
2898
+ }
2899
+
2900
+ .df-review-section-outline-list {
2901
+ display: grid;
2902
+ align-content: start;
2903
+ gap: 2px;
2904
+ min-height: 0;
2905
+ overflow-y: auto;
2906
+ padding: 8px 1px 8px 15px;
2907
+ scrollbar-gutter: stable;
2908
+ }
2909
+
2910
+ .df-review-section-outline-item {
2911
+ display: grid;
2912
+ }
2913
+
2914
+ .df-review-section-outline-item.is-depth-1:not(:last-child) {
2915
+ margin-bottom: 7px;
2916
+ padding-bottom: 7px;
2917
+ border-bottom: 1px solid var(--df-review-line-soft);
2918
+ }
2919
+
2920
+ .df-review-section-outline-row {
2921
+ display: grid;
2922
+ grid-template-columns: 18px minmax(0, 1fr) auto;
2923
+ align-items: center;
2924
+ gap: 5px;
2925
+ border-radius: var(--df-review-radius-sm);
2926
+ padding: 7px 6px;
2927
+ }
2928
+
2929
+ .df-review-section-outline-row:hover {
2930
+ background: var(--df-review-accent-soft);
2931
+ }
2932
+
2933
+ .df-review-section-outline-toggle {
2934
+ display: grid;
2935
+ place-items: center;
2936
+ width: 18px;
2937
+ height: 18px;
2938
+ border: 0;
2939
+ border-radius: var(--df-review-radius-sm);
2940
+ padding: 0;
2941
+ color: var(--df-review-muted);
2942
+ background: transparent;
2943
+ }
2944
+
2945
+ .df-review-section-outline-toggle:hover {
2946
+ color: var(--df-review-text);
2947
+ background: var(--df-review-accent-soft);
2948
+ }
2949
+
2950
+ .df-review-section-outline-toggle svg {
2951
+ width: 13px;
2952
+ height: 13px;
2953
+ fill: none;
2954
+ stroke: currentColor;
2955
+ stroke-linecap: round;
2956
+ stroke-linejoin: round;
2957
+ stroke-width: 2.4;
2958
+ transition: transform 120ms ease;
2959
+ }
2960
+
2961
+ .df-review-section-outline-toggle.is-collapsed svg {
2962
+ transform: rotate(-90deg);
2963
+ }
2964
+
2965
+ .df-review-section-outline-toggle.is-placeholder {
2966
+ pointer-events: none;
2967
+ }
2968
+
2969
+ .df-review-section-outline-name {
2970
+ display: grid;
2971
+ gap: 2px;
2972
+ min-width: 0;
2973
+ overflow: visible;
2974
+ border: 0;
2975
+ padding: 0;
2976
+ color: var(--df-review-text);
2977
+ background: transparent;
2978
+ font-size: var(--df-review-font-size-sm);
2979
+ font-weight: 500;
2980
+ text-align: left;
2981
+ white-space: normal;
2982
+ cursor: pointer;
2983
+ }
2984
+
2985
+ .df-review-section-outline-name span,
2986
+ .df-review-section-outline-name small {
2987
+ overflow: visible;
2988
+ overflow-wrap: anywhere;
2989
+ text-overflow: clip;
2990
+ white-space: normal;
2991
+ }
2992
+
2993
+ .df-review-section-outline-name small {
2994
+ color: var(--df-review-muted);
2995
+ font-family: var(--df-review-font-mono);
2996
+ font-size: var(--df-review-font-size-xs);
2997
+ font-weight: 500;
2998
+ }
2999
+
3000
+ .df-review-section-outline-name:hover {
3001
+ color: var(--df-review-accent);
3002
+ }
3003
+
3004
+ .df-review-section-outline-links {
3005
+ display: inline-flex;
3006
+ align-items: center;
3007
+ justify-content: flex-end;
3008
+ gap: 2px;
3009
+ }
3010
+
3011
+ .df-review-section-outline-divider {
3012
+ color: var(--df-review-subtle);
3013
+ font-size: var(--df-review-font-size-xs);
3014
+ line-height: 1;
3015
+ user-select: none;
3016
+ }
3017
+
3018
+ .df-review-section-outline-link {
3019
+ display: inline-grid;
3020
+ place-items: center;
3021
+ width: 26px;
3022
+ height: 26px;
3023
+ border: 1px solid transparent;
3024
+ border-radius: var(--df-review-radius-sm);
3025
+ padding: 0;
3026
+ color: var(--df-review-muted);
3027
+ background: transparent;
3028
+ cursor: pointer;
3029
+ transition: border-color 140ms ease, background 140ms ease,
3030
+ color 140ms ease, opacity 140ms ease;
3031
+ }
3032
+
3033
+ .df-review-section-outline-link.is-dom-select {
3034
+ width: 26px;
3035
+ min-width: 26px;
3036
+ padding: 0;
3037
+ }
3038
+
3039
+ .df-review-section-outline-link.is-dom-select svg {
3040
+ width: 16px;
3041
+ height: 16px;
3042
+ }
3043
+
3044
+ .df-review-section-outline-link:hover {
3045
+ border-color: rgba(124, 199, 255, 0.34);
3046
+ color: var(--df-review-accent);
3047
+ background: var(--df-review-accent-soft);
3048
+ }
3049
+
3050
+ .df-review-section-outline-link svg {
3051
+ width: 14px;
3052
+ height: 14px;
3053
+ fill: none;
3054
+ stroke: currentColor;
3055
+ stroke-linecap: round;
3056
+ stroke-linejoin: round;
3057
+ stroke-width: 2;
3058
+ }
3059
+
3060
+ .df-review-section-outline-link:disabled {
3061
+ color: var(--df-review-subtle);
3062
+ cursor: not-allowed;
3063
+ opacity: 0.42;
3064
+ }
3065
+
3066
+ .df-review-section-outline-link:disabled:hover {
3067
+ border-color: transparent;
3068
+ color: var(--df-review-subtle);
3069
+ background: transparent;
3070
+ }
3071
+
3072
+ .df-review-section-outline-children {
3073
+ display: grid;
3074
+ }
3075
+
3076
+ .df-review-section-outline-empty {
3077
+ padding: 14px 12px 16px;
3078
+ color: var(--df-review-muted);
3079
+ font-size: var(--df-review-font-size-xs);
3080
+ font-weight: 500;
3081
+ }
3082
+
3083
+ .df-review-device-frame {
3084
+ position: relative;
3085
+ box-sizing: border-box;
3086
+ flex: 0 0 auto;
3087
+ }
3088
+
3089
+ .df-review-ruler-corner {
3090
+ position: absolute;
3091
+ left: -26px;
3092
+ top: -26px;
3093
+ width: 26px;
3094
+ height: 26px;
3095
+ z-index: 6;
3096
+ border-right: 1px solid var(--df-review-line-soft);
3097
+ border-bottom: 1px solid var(--df-review-line-soft);
3098
+ background: var(--df-review-color-ruler-surface);
3099
+ }
3100
+
3101
+ .df-review-ruler-gutter {
3102
+ position: absolute;
3103
+ z-index: 6;
3104
+ background: var(--df-review-color-ruler-surface);
3105
+ color: var(--df-review-muted);
3106
+ user-select: none;
3107
+ }
3108
+
3109
+ .df-review-ruler-gutter.is-x {
3110
+ left: 0;
3111
+ right: 0;
3112
+ top: -26px;
3113
+ height: 26px;
3114
+ border-bottom: 1px solid var(--df-review-line-soft);
3115
+ background-image:
3116
+ linear-gradient(
3117
+ to right,
3118
+ var(--df-review-color-ruler-tick-major) 1px,
3119
+ transparent 1px
3120
+ ),
3121
+ linear-gradient(
3122
+ to right,
3123
+ var(--df-review-color-ruler-tick-minor) 1px,
3124
+ transparent 1px
3125
+ );
3126
+ background-size:
3127
+ calc(var(--df-review-ruler-step-x) * 5) 11px,
3128
+ var(--df-review-ruler-step-x) 6px;
3129
+ background-position: left bottom;
3130
+ background-repeat: repeat-x;
3131
+ }
3132
+
3133
+ .df-review-ruler-gutter.is-y {
3134
+ left: -26px;
3135
+ top: 0;
3136
+ bottom: 0;
3137
+ width: 26px;
3138
+ border-right: 1px solid var(--df-review-line-soft);
3139
+ background-image:
3140
+ linear-gradient(
3141
+ to bottom,
3142
+ var(--df-review-color-ruler-tick-major) 1px,
3143
+ transparent 1px
3144
+ ),
3145
+ linear-gradient(
3146
+ to bottom,
3147
+ var(--df-review-color-ruler-tick-minor) 1px,
3148
+ transparent 1px
3149
+ );
3150
+ background-size:
3151
+ 11px calc(var(--df-review-ruler-step-y) * 5),
3152
+ 6px var(--df-review-ruler-step-y);
3153
+ background-position: right top;
3154
+ background-repeat: repeat-y;
3155
+ }
3156
+
3157
+ .df-review-ruler-frame-label {
3158
+ position: absolute;
3159
+ right: 6px;
3160
+ top: 50%;
3161
+ transform: translateY(-50%);
3162
+ display: flex;
3163
+ align-items: center;
3164
+ gap: 6px;
3165
+ padding: 4px 8px;
3166
+ border: 1px solid var(--df-review-color-ruler-popover-border);
3167
+ border-radius: var(--df-review-radius-sm);
3168
+ background: var(--df-review-color-ruler-label);
3169
+ line-height: 1;
3170
+ white-space: nowrap;
3171
+ box-shadow: 0 6px 18px var(--df-review-color-ruler-popover-shadow);
3172
+ }
3173
+
3174
+ .df-review-ruler-frame-label strong {
3175
+ color: var(--df-review-color-ruler-label-text);
3176
+ font-size: var(--df-review-font-size-xs);
3177
+ font-weight: 600;
3178
+ }
3179
+
3180
+ .df-review-ruler-frame-label span {
3181
+ color: var(--df-review-color-ruler-label-text);
3182
+ font-size: var(--df-review-font-size-xs);
3183
+ font-weight: 600;
3184
+ opacity: 0.78;
3185
+ }
3186
+
3187
+ .df-review-ruler-coord {
3188
+ position: absolute;
3189
+ z-index: 7;
3190
+ padding: 4px 6px;
3191
+ border: 1px solid var(--df-review-color-ruler-popover-border);
3192
+ border-radius: var(--df-review-radius-xs);
3193
+ background: var(--df-review-color-ruler-coord-bg);
3194
+ color: var(--df-review-color-ruler-coord-text);
3195
+ font-size: var(--df-review-font-size-xs);
3196
+ font-weight: 600;
3197
+ line-height: 1;
3198
+ white-space: nowrap;
3199
+ pointer-events: none;
3200
+ box-shadow: 0 6px 18px var(--df-review-color-ruler-popover-shadow);
3201
+ }
3202
+
3203
+ .df-review-ruler-coord.is-x {
3204
+ top: 50%;
3205
+ transform: translate(-50%, -50%);
3206
+ }
3207
+
3208
+ .df-review-ruler-coord.is-y {
3209
+ left: 50%;
3210
+ transform: translate(-50%, -50%);
3211
+ }
3212
+
3213
+ .df-review-ruler-overlay {
3214
+ position: absolute;
3215
+ left: 0;
3216
+ top: 0;
3217
+ width: inherit;
3218
+ height: inherit;
3219
+ z-index: 5;
3220
+ cursor: crosshair;
3221
+ overflow: hidden;
3222
+ touch-action: none;
3223
+ user-select: none;
3224
+ }
3225
+
3226
+ .df-review-ruler-overlay.is-dragging {
3227
+ cursor: crosshair;
3228
+ }
3229
+
3230
+ .df-review-ruler-guide {
3231
+ position: absolute;
3232
+ z-index: 2;
3233
+ pointer-events: none;
3234
+ background: var(--df-review-color-ruler-guide);
3235
+ box-shadow: 0 0 0 1px var(--df-review-color-ruler-measure-shadow);
2698
3236
  }
2699
3237
 
2700
3238
  .df-review-ruler-guide.is-x {
@@ -2733,7 +3271,7 @@ function ensureReviewShellStyle() {
2733
3271
  color: var(--df-review-color-ruler-popover-text);
2734
3272
  font-family: var(--df-review-font-mono);
2735
3273
  font-size: var(--df-review-font-size-lg);
2736
- font-weight: 900;
3274
+ font-weight: 600;
2737
3275
  line-height: 1;
2738
3276
  white-space: nowrap;
2739
3277
  letter-spacing: -0.02em;
@@ -2746,12 +3284,12 @@ function ensureReviewShellStyle() {
2746
3284
  @media (max-width: 860px) {
2747
3285
  .df-review-shell,
2748
3286
  .df-review-shell.is-list-visible {
2749
- grid-template-columns: minmax(0, 1fr) 0 32px;
3287
+ grid-template-columns: minmax(0, 1fr) 0 48px;
2750
3288
  grid-template-rows: auto auto minmax(0, 1fr);
2751
3289
  }
2752
3290
 
2753
3291
  .df-review-shell.is-list-visible {
2754
- grid-template-columns: minmax(0, 1fr) minmax(260px, 70vw) 32px;
3292
+ grid-template-columns: minmax(0, 1fr) minmax(260px, 70vw) 48px;
2755
3293
  }
2756
3294
 
2757
3295
  .df-review-qa-panel {
@@ -2760,7 +3298,7 @@ function ensureReviewShellStyle() {
2760
3298
  }
2761
3299
 
2762
3300
  .df-review-tools {
2763
- flex-wrap: wrap;
3301
+ flex-wrap: nowrap;
2764
3302
  }
2765
3303
 
2766
3304
  .df-review-tool-controls {
@@ -2790,11 +3328,19 @@ function ensureReviewShellStyle() {
2790
3328
  max-height: calc(100vh - 270px);
2791
3329
  }
2792
3330
 
2793
- .df-review-panel-body {
2794
- min-height: 0;
2795
- }
2796
- }
2797
- `;
3331
+ .df-review-panel-body {
3332
+ min-height: 0;
3333
+ }
3334
+ }
3335
+
3336
+ @media (hover: none) and (pointer: coarse) {
3337
+ .df-review-edit-textarea textarea,
3338
+ .df-review-preset-select,
3339
+ .df-review-prompt-block textarea {
3340
+ font-size: 16px;
3341
+ }
3342
+ }
3343
+ `;
2798
3344
  document.head.append(style);
2799
3345
  }
2800
3346
  }
@@ -2917,31 +3463,59 @@ var __iconNode = [
2917
3463
  ];
2918
3464
  var Bot = createLucideIcon("bot", __iconNode);
2919
3465
 
3466
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/chevron-down.mjs
3467
+ var __iconNode2 = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
3468
+ var ChevronDown = createLucideIcon("chevron-down", __iconNode2);
3469
+
2920
3470
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/circle-question-mark.mjs
2921
- var __iconNode2 = [
3471
+ var __iconNode3 = [
2922
3472
  ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
2923
3473
  ["path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3", key: "1u773s" }],
2924
3474
  ["path", { d: "M12 17h.01", key: "p32p05" }]
2925
3475
  ];
2926
- var CircleQuestionMark = createLucideIcon("circle-question-mark", __iconNode2);
3476
+ var CircleQuestionMark = createLucideIcon("circle-question-mark", __iconNode3);
3477
+
3478
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/code-xml.mjs
3479
+ var __iconNode4 = [
3480
+ ["path", { d: "m18 16 4-4-4-4", key: "1inbqp" }],
3481
+ ["path", { d: "m6 8-4 4 4 4", key: "15zrgr" }],
3482
+ ["path", { d: "m14.5 4-5 16", key: "e7oirm" }]
3483
+ ];
3484
+ var CodeXml = createLucideIcon("code-xml", __iconNode4);
2927
3485
 
2928
3486
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/copy.mjs
2929
- var __iconNode3 = [
3487
+ var __iconNode5 = [
2930
3488
  ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
2931
3489
  ["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" }]
2932
3490
  ];
2933
- var Copy = createLucideIcon("copy", __iconNode3);
3491
+ var Copy = createLucideIcon("copy", __iconNode5);
3492
+
3493
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/database.mjs
3494
+ var __iconNode6 = [
3495
+ ["ellipse", { cx: "12", cy: "5", rx: "9", ry: "3", key: "msslwz" }],
3496
+ ["path", { d: "M3 5V19A9 3 0 0 0 21 19V5", key: "1wlel7" }],
3497
+ ["path", { d: "M3 12A9 3 0 0 0 21 12", key: "mv7ke4" }]
3498
+ ];
3499
+ var Database = createLucideIcon("database", __iconNode6);
3500
+
3501
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/ellipsis.mjs
3502
+ var __iconNode7 = [
3503
+ ["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }],
3504
+ ["circle", { cx: "19", cy: "12", r: "1", key: "1wjl8i" }],
3505
+ ["circle", { cx: "5", cy: "12", r: "1", key: "1pcz8c" }]
3506
+ ];
3507
+ var Ellipsis = createLucideIcon("ellipsis", __iconNode7);
2934
3508
 
2935
3509
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/external-link.mjs
2936
- var __iconNode4 = [
3510
+ var __iconNode8 = [
2937
3511
  ["path", { d: "M15 3h6v6", key: "1q9fwt" }],
2938
3512
  ["path", { d: "M10 14 21 3", key: "gplh6r" }],
2939
3513
  ["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6", key: "a6xqqp" }]
2940
3514
  ];
2941
- var ExternalLink = createLucideIcon("external-link", __iconNode4);
3515
+ var ExternalLink = createLucideIcon("external-link", __iconNode8);
2942
3516
 
2943
3517
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/eye-off.mjs
2944
- var __iconNode5 = [
3518
+ var __iconNode9 = [
2945
3519
  [
2946
3520
  "path",
2947
3521
  {
@@ -2959,10 +3533,10 @@ var __iconNode5 = [
2959
3533
  ],
2960
3534
  ["path", { d: "m2 2 20 20", key: "1ooewy" }]
2961
3535
  ];
2962
- var EyeOff = createLucideIcon("eye-off", __iconNode5);
3536
+ var EyeOff = createLucideIcon("eye-off", __iconNode9);
2963
3537
 
2964
3538
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/eye.mjs
2965
- var __iconNode6 = [
3539
+ var __iconNode10 = [
2966
3540
  [
2967
3541
  "path",
2968
3542
  {
@@ -2972,54 +3546,59 @@ var __iconNode6 = [
2972
3546
  ],
2973
3547
  ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
2974
3548
  ];
2975
- var Eye = createLucideIcon("eye", __iconNode6);
3549
+ var Eye = createLucideIcon("eye", __iconNode10);
2976
3550
 
2977
- // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/grip-vertical.mjs
2978
- var __iconNode7 = [
2979
- ["circle", { cx: "9", cy: "12", r: "1", key: "1vctgf" }],
2980
- ["circle", { cx: "9", cy: "5", r: "1", key: "hp0tcf" }],
2981
- ["circle", { cx: "9", cy: "19", r: "1", key: "fkjjf6" }],
2982
- ["circle", { cx: "15", cy: "12", r: "1", key: "1tmaij" }],
2983
- ["circle", { cx: "15", cy: "5", r: "1", key: "19l28e" }],
2984
- ["circle", { cx: "15", cy: "19", r: "1", key: "f4zoj3" }]
3551
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/file-text.mjs
3552
+ var __iconNode11 = [
3553
+ [
3554
+ "path",
3555
+ {
3556
+ d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
3557
+ key: "1oefj6"
3558
+ }
3559
+ ],
3560
+ ["path", { d: "M14 2v5a1 1 0 0 0 1 1h5", key: "wfsgrz" }],
3561
+ ["path", { d: "M10 9H8", key: "b1mrlr" }],
3562
+ ["path", { d: "M16 13H8", key: "t4e002" }],
3563
+ ["path", { d: "M16 17H8", key: "z1uh3a" }]
2985
3564
  ];
2986
- var GripVertical = createLucideIcon("grip-vertical", __iconNode7);
3565
+ var FileText = createLucideIcon("file-text", __iconNode11);
2987
3566
 
2988
3567
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/image.mjs
2989
- var __iconNode8 = [
3568
+ var __iconNode12 = [
2990
3569
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
2991
3570
  ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
2992
3571
  ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
2993
3572
  ];
2994
- var Image = createLucideIcon("image", __iconNode8);
3573
+ var Image = createLucideIcon("image", __iconNode12);
2995
3574
 
2996
3575
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/layout-grid.mjs
2997
- var __iconNode9 = [
3576
+ var __iconNode13 = [
2998
3577
  ["rect", { width: "7", height: "7", x: "3", y: "3", rx: "1", key: "1g98yp" }],
2999
3578
  ["rect", { width: "7", height: "7", x: "14", y: "3", rx: "1", key: "6d4xhi" }],
3000
3579
  ["rect", { width: "7", height: "7", x: "14", y: "14", rx: "1", key: "nxv5o0" }],
3001
3580
  ["rect", { width: "7", height: "7", x: "3", y: "14", rx: "1", key: "1bb6yr" }]
3002
3581
  ];
3003
- var LayoutGrid = createLucideIcon("layout-grid", __iconNode9);
3582
+ var LayoutGrid = createLucideIcon("layout-grid", __iconNode13);
3004
3583
 
3005
3584
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/link-2.mjs
3006
- var __iconNode10 = [
3585
+ var __iconNode14 = [
3007
3586
  ["path", { d: "M9 17H7A5 5 0 0 1 7 7h2", key: "8i5ue5" }],
3008
3587
  ["path", { d: "M15 7h2a5 5 0 1 1 0 10h-2", key: "1b9ql8" }],
3009
3588
  ["line", { x1: "8", x2: "16", y1: "12", y2: "12", key: "1jonct" }]
3010
3589
  ];
3011
- var Link2 = createLucideIcon("link-2", __iconNode10);
3590
+ var Link2 = createLucideIcon("link-2", __iconNode14);
3012
3591
 
3013
3592
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/list-filter.mjs
3014
- var __iconNode11 = [
3593
+ var __iconNode15 = [
3015
3594
  ["path", { d: "M2 5h20", key: "1fs1ex" }],
3016
3595
  ["path", { d: "M6 12h12", key: "8npq4p" }],
3017
3596
  ["path", { d: "M9 19h6", key: "456am0" }]
3018
3597
  ];
3019
- var ListFilter = createLucideIcon("list-filter", __iconNode11);
3598
+ var ListFilter = createLucideIcon("list-filter", __iconNode15);
3020
3599
 
3021
3600
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/map.mjs
3022
- var __iconNode12 = [
3601
+ var __iconNode16 = [
3023
3602
  [
3024
3603
  "path",
3025
3604
  {
@@ -3030,27 +3609,39 @@ var __iconNode12 = [
3030
3609
  ["path", { d: "M15 5.764v15", key: "1pn4in" }],
3031
3610
  ["path", { d: "M9 3.236v15", key: "1uimfh" }]
3032
3611
  ];
3033
- var Map2 = createLucideIcon("map", __iconNode12);
3612
+ var Map2 = createLucideIcon("map", __iconNode16);
3034
3613
 
3035
3614
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/maximize-2.mjs
3036
- var __iconNode13 = [
3615
+ var __iconNode17 = [
3037
3616
  ["path", { d: "M15 3h6v6", key: "1q9fwt" }],
3038
3617
  ["path", { d: "m21 3-7 7", key: "1l2asr" }],
3039
3618
  ["path", { d: "m3 21 7-7", key: "tjx5ai" }],
3040
3619
  ["path", { d: "M9 21H3v-6", key: "wtvkvv" }]
3041
3620
  ];
3042
- var Maximize2 = createLucideIcon("maximize-2", __iconNode13);
3621
+ var Maximize2 = createLucideIcon("maximize-2", __iconNode17);
3043
3622
 
3044
3623
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/monitor.mjs
3045
- var __iconNode14 = [
3624
+ var __iconNode18 = [
3046
3625
  ["rect", { width: "20", height: "14", x: "2", y: "3", rx: "2", key: "48i651" }],
3047
3626
  ["line", { x1: "8", x2: "16", y1: "21", y2: "21", key: "1svkeh" }],
3048
3627
  ["line", { x1: "12", x2: "12", y1: "17", y2: "21", key: "vw1qmm" }]
3049
3628
  ];
3050
- var Monitor = createLucideIcon("monitor", __iconNode14);
3629
+ var Monitor = createLucideIcon("monitor", __iconNode18);
3630
+
3631
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/moon.mjs
3632
+ var __iconNode19 = [
3633
+ [
3634
+ "path",
3635
+ {
3636
+ d: "M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401",
3637
+ key: "kfwtm"
3638
+ }
3639
+ ]
3640
+ ];
3641
+ var Moon = createLucideIcon("moon", __iconNode19);
3051
3642
 
3052
3643
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/pencil.mjs
3053
- var __iconNode15 = [
3644
+ var __iconNode20 = [
3054
3645
  [
3055
3646
  "path",
3056
3647
  {
@@ -3060,25 +3651,25 @@ var __iconNode15 = [
3060
3651
  ],
3061
3652
  ["path", { d: "m15 5 4 4", key: "1mk7zo" }]
3062
3653
  ];
3063
- var Pencil = createLucideIcon("pencil", __iconNode15);
3654
+ var Pencil = createLucideIcon("pencil", __iconNode20);
3064
3655
 
3065
3656
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/rectangle-horizontal.mjs
3066
- var __iconNode16 = [
3657
+ var __iconNode21 = [
3067
3658
  ["rect", { width: "20", height: "12", x: "2", y: "6", rx: "2", key: "9lu3g6" }]
3068
3659
  ];
3069
- var RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode16);
3660
+ var RectangleHorizontal = createLucideIcon("rectangle-horizontal", __iconNode21);
3070
3661
 
3071
3662
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/refresh-cw.mjs
3072
- var __iconNode17 = [
3663
+ var __iconNode22 = [
3073
3664
  ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }],
3074
3665
  ["path", { d: "M21 3v5h-5", key: "1q7to0" }],
3075
3666
  ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }],
3076
3667
  ["path", { d: "M8 16H3v5", key: "1cv678" }]
3077
3668
  ];
3078
- var RefreshCw = createLucideIcon("refresh-cw", __iconNode17);
3669
+ var RefreshCw = createLucideIcon("refresh-cw", __iconNode22);
3079
3670
 
3080
3671
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/ruler.mjs
3081
- var __iconNode18 = [
3672
+ var __iconNode23 = [
3082
3673
  [
3083
3674
  "path",
3084
3675
  {
@@ -3091,19 +3682,26 @@ var __iconNode18 = [
3091
3682
  ["path", { d: "m8.5 6.5 2-2", key: "vc6u1g" }],
3092
3683
  ["path", { d: "m17.5 15.5 2-2", key: "wo5hmg" }]
3093
3684
  ];
3094
- var Ruler = createLucideIcon("ruler", __iconNode18);
3685
+ var Ruler = createLucideIcon("ruler", __iconNode23);
3095
3686
 
3096
3687
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/scan.mjs
3097
- var __iconNode19 = [
3688
+ var __iconNode24 = [
3098
3689
  ["path", { d: "M3 7V5a2 2 0 0 1 2-2h2", key: "aa7l1z" }],
3099
3690
  ["path", { d: "M17 3h2a2 2 0 0 1 2 2v2", key: "4qcy5o" }],
3100
3691
  ["path", { d: "M21 17v2a2 2 0 0 1-2 2h-2", key: "6vwrx8" }],
3101
3692
  ["path", { d: "M7 21H5a2 2 0 0 1-2-2v-2", key: "ioqczr" }]
3102
3693
  ];
3103
- var Scan = createLucideIcon("scan", __iconNode19);
3694
+ var Scan = createLucideIcon("scan", __iconNode24);
3695
+
3696
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/search.mjs
3697
+ var __iconNode25 = [
3698
+ ["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
3699
+ ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
3700
+ ];
3701
+ var Search = createLucideIcon("search", __iconNode25);
3104
3702
 
3105
3703
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/settings.mjs
3106
- var __iconNode20 = [
3704
+ var __iconNode26 = [
3107
3705
  [
3108
3706
  "path",
3109
3707
  {
@@ -3113,17 +3711,17 @@ var __iconNode20 = [
3113
3711
  ],
3114
3712
  ["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
3115
3713
  ];
3116
- var Settings = createLucideIcon("settings", __iconNode20);
3714
+ var Settings = createLucideIcon("settings", __iconNode26);
3117
3715
 
3118
3716
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/smartphone.mjs
3119
- var __iconNode21 = [
3717
+ var __iconNode27 = [
3120
3718
  ["rect", { width: "14", height: "20", x: "5", y: "2", rx: "2", ry: "2", key: "1yt0o3" }],
3121
3719
  ["path", { d: "M12 18h.01", key: "mhygvu" }]
3122
3720
  ];
3123
- var Smartphone = createLucideIcon("smartphone", __iconNode21);
3721
+ var Smartphone = createLucideIcon("smartphone", __iconNode27);
3124
3722
 
3125
3723
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/square-mouse-pointer.mjs
3126
- var __iconNode22 = [
3724
+ var __iconNode28 = [
3127
3725
  [
3128
3726
  "path",
3129
3727
  {
@@ -3133,35 +3731,249 @@ var __iconNode22 = [
3133
3731
  ],
3134
3732
  ["path", { d: "M21 11V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h6", key: "14rsvq" }]
3135
3733
  ];
3136
- var SquareMousePointer = createLucideIcon("square-mouse-pointer", __iconNode22);
3734
+ var SquareMousePointer = createLucideIcon("square-mouse-pointer", __iconNode28);
3137
3735
 
3138
3736
  // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/sticky-note.mjs
3139
- var __iconNode23 = [
3737
+ var __iconNode29 = [
3140
3738
  [
3141
3739
  "path",
3142
3740
  {
3143
- d: "M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",
3144
- key: "1dfntj"
3145
- }
3146
- ],
3147
- ["path", { d: "M15 3v5a1 1 0 0 0 1 1h5", key: "6s6qgf" }]
3148
- ];
3149
- var StickyNote = createLucideIcon("sticky-note", __iconNode23);
3150
-
3151
- // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/upload.mjs
3152
- var __iconNode24 = [
3153
- ["path", { d: "M12 3v12", key: "1x0j5s" }],
3154
- ["path", { d: "m17 8-5-5-5 5", key: "7q97r8" }],
3155
- ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }]
3156
- ];
3157
- var Upload = createLucideIcon("upload", __iconNode24);
3741
+ d: "M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",
3742
+ key: "1dfntj"
3743
+ }
3744
+ ],
3745
+ ["path", { d: "M15 3v5a1 1 0 0 0 1 1h5", key: "6s6qgf" }]
3746
+ ];
3747
+ var StickyNote = createLucideIcon("sticky-note", __iconNode29);
3748
+
3749
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/sun.mjs
3750
+ var __iconNode30 = [
3751
+ ["circle", { cx: "12", cy: "12", r: "4", key: "4exip2" }],
3752
+ ["path", { d: "M12 2v2", key: "tus03m" }],
3753
+ ["path", { d: "M12 20v2", key: "1lh1kg" }],
3754
+ ["path", { d: "m4.93 4.93 1.41 1.41", key: "149t6j" }],
3755
+ ["path", { d: "m17.66 17.66 1.41 1.41", key: "ptbguv" }],
3756
+ ["path", { d: "M2 12h2", key: "1t8f8n" }],
3757
+ ["path", { d: "M20 12h2", key: "1q8mjw" }],
3758
+ ["path", { d: "m6.34 17.66-1.41 1.41", key: "1m8zz5" }],
3759
+ ["path", { d: "m19.07 4.93-1.41 1.41", key: "1shlcs" }]
3760
+ ];
3761
+ var Sun = createLucideIcon("sun", __iconNode30);
3762
+
3763
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/upload.mjs
3764
+ var __iconNode31 = [
3765
+ ["path", { d: "M12 3v12", key: "1x0j5s" }],
3766
+ ["path", { d: "m17 8-5-5-5 5", key: "7q97r8" }],
3767
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }]
3768
+ ];
3769
+ var Upload = createLucideIcon("upload", __iconNode31);
3770
+
3771
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/user-round.mjs
3772
+ var __iconNode32 = [
3773
+ ["circle", { cx: "12", cy: "8", r: "5", key: "1hypcn" }],
3774
+ ["path", { d: "M20 21a8 8 0 0 0-16 0", key: "rfgkzh" }]
3775
+ ];
3776
+ var UserRound = createLucideIcon("user-round", __iconNode32);
3777
+
3778
+ // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/x.mjs
3779
+ var __iconNode33 = [
3780
+ ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
3781
+ ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
3782
+ ];
3783
+ var X = createLucideIcon("x", __iconNode33);
3784
+
3785
+ // src/core/geometry.ts
3786
+ function rectanglesIntersect(a, b) {
3787
+ return a.left < b.left + b.width && a.left + a.width > b.left && a.top < b.top + b.height && a.top + a.height > b.top;
3788
+ }
3789
+ function getPointSelection(point) {
3790
+ return {
3791
+ left: point.x,
3792
+ top: point.y,
3793
+ width: 1,
3794
+ height: 1
3795
+ };
3796
+ }
3797
+ function toViewportSelection(selection) {
3798
+ return {
3799
+ left: selection.x,
3800
+ top: selection.y,
3801
+ width: selection.width,
3802
+ height: selection.height
3803
+ };
3804
+ }
3805
+ function toPublicSelection(selection) {
3806
+ return {
3807
+ x: Math.round(selection.left),
3808
+ y: Math.round(selection.top),
3809
+ width: Math.round(selection.width),
3810
+ height: Math.round(selection.height)
3811
+ };
3812
+ }
3813
+ function getSelectionCenter(selection) {
3814
+ if ("left" in selection) {
3815
+ return {
3816
+ x: selection.left + selection.width / 2,
3817
+ y: selection.top + selection.height / 2
3818
+ };
3819
+ }
3820
+ return {
3821
+ x: selection.x + selection.width / 2,
3822
+ y: selection.y + selection.height / 2
3823
+ };
3824
+ }
3825
+ function isRelativeSelection(value) {
3826
+ if (!value || typeof value !== "object") return false;
3827
+ const selection = value;
3828
+ return typeof selection.x === "number" && typeof selection.y === "number" && typeof selection.width === "number" && typeof selection.height === "number";
3829
+ }
3830
+ function getViewportSize(environment) {
3831
+ const targetWindow = environment?.window ?? window;
3832
+ return {
3833
+ width: targetWindow.innerWidth,
3834
+ height: targetWindow.innerHeight
3835
+ };
3836
+ }
3837
+ function isPointInViewport(point, environment) {
3838
+ const viewport = getViewportSize(environment);
3839
+ return point.x >= 0 && point.y >= 0 && point.x <= viewport.width && point.y <= viewport.height;
3840
+ }
3841
+ function isSelectionInViewport(selection, environment) {
3842
+ const viewport = getViewportSize(environment);
3843
+ return rectanglesIntersect(selection, {
3844
+ left: 0,
3845
+ top: 0,
3846
+ width: viewport.width,
3847
+ height: viewport.height
3848
+ });
3849
+ }
3850
+ function clampPoint(point, environment) {
3851
+ const viewport = getViewportSize(environment);
3852
+ return {
3853
+ x: clamp(point.x, 0, viewport.width),
3854
+ y: clamp(point.y, 0, viewport.height)
3855
+ };
3856
+ }
3857
+ function getPopoverPosition(point, environment, options) {
3858
+ const bounds = getPopoverBounds(environment);
3859
+ const margin = 12;
3860
+ const width = Math.min(
3861
+ options?.width ?? 320,
3862
+ Math.max(240, bounds.width - margin * 2)
3863
+ );
3864
+ const estimatedHeight = options?.estimatedHeight ?? 178;
3865
+ const offset = options?.offset ?? 12;
3866
+ return {
3867
+ left: clamp(
3868
+ point.x + offset,
3869
+ bounds.left + margin,
3870
+ bounds.left + bounds.width - width - margin
3871
+ ),
3872
+ top: clamp(
3873
+ point.y + offset,
3874
+ bounds.top + margin,
3875
+ bounds.top + bounds.height - estimatedHeight - margin
3876
+ )
3877
+ };
3878
+ }
3879
+ function getPopoverBounds(environment) {
3880
+ if (!environment) {
3881
+ return {
3882
+ left: 0,
3883
+ top: 0,
3884
+ width: window.innerWidth,
3885
+ height: window.innerHeight
3886
+ };
3887
+ }
3888
+ return environment.overlayRect;
3889
+ }
3890
+ function toHostPoint(point, environment) {
3891
+ if (!environment) return point;
3892
+ return {
3893
+ x: point.x + environment.viewportRect.left,
3894
+ y: point.y + environment.viewportRect.top
3895
+ };
3896
+ }
3897
+ function toHostSelection(selection, environment) {
3898
+ return {
3899
+ left: selection.left + environment.viewportRect.left,
3900
+ top: selection.top + environment.viewportRect.top,
3901
+ width: selection.width,
3902
+ height: selection.height
3903
+ };
3904
+ }
3905
+ function toTargetPoint(point, environment) {
3906
+ if (!environment) return point;
3907
+ return {
3908
+ x: point.x - environment.viewportRect.left,
3909
+ y: point.y - environment.viewportRect.top
3910
+ };
3911
+ }
3912
+ function toTargetPointFromHostEvent(event, environment) {
3913
+ return toTargetPoint(
3914
+ {
3915
+ x: event.clientX,
3916
+ y: event.clientY
3917
+ },
3918
+ environment
3919
+ );
3920
+ }
3921
+ function placeLayerOverTarget(layer, environment) {
3922
+ layer.style.left = `${environment.viewportRect.left}px`;
3923
+ layer.style.top = `${environment.viewportRect.top}px`;
3924
+ layer.style.width = `${environment.viewportRect.width}px`;
3925
+ layer.style.height = `${environment.viewportRect.height}px`;
3926
+ layer.style.right = "auto";
3927
+ layer.style.bottom = "auto";
3928
+ }
3929
+ function clamp(value, min, max) {
3930
+ return Math.min(Math.max(value, min), Math.max(min, max));
3931
+ }
3932
+ function roundRatio(value) {
3933
+ return Math.round(value * 1e4) / 1e4;
3934
+ }
3935
+ function roundPoint(point) {
3936
+ return {
3937
+ x: Math.round(point.x),
3938
+ y: Math.round(point.y)
3939
+ };
3940
+ }
3158
3941
 
3159
- // node_modules/.pnpm/lucide-react@1.20.0_react@19.2.7/node_modules/lucide-react/dist/esm/icons/x.mjs
3160
- var __iconNode25 = [
3161
- ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
3162
- ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
3163
- ];
3164
- var X = createLucideIcon("x", __iconNode25);
3942
+ // src/core/scroll.ts
3943
+ function waitForNextFrame(environment) {
3944
+ return new Promise((resolve) => {
3945
+ (environment?.window ?? window).requestAnimationFrame(() => resolve());
3946
+ });
3947
+ }
3948
+ function runWithAutoScrollBehavior(targetDocument, callback) {
3949
+ const elements = [
3950
+ targetDocument.documentElement,
3951
+ targetDocument.body
3952
+ ].filter((element) => Boolean(element));
3953
+ const previousValues = elements.map((element) => element.style.scrollBehavior);
3954
+ elements.forEach((element) => {
3955
+ element.style.scrollBehavior = "auto";
3956
+ });
3957
+ try {
3958
+ callback();
3959
+ } finally {
3960
+ elements.forEach((element, index) => {
3961
+ element.style.scrollBehavior = previousValues[index] ?? "";
3962
+ });
3963
+ }
3964
+ }
3965
+ function setDocumentScrollInstantly(environment, position) {
3966
+ const scrollElement = environment.document.scrollingElement;
3967
+ if (scrollElement) {
3968
+ scrollElement.scrollLeft = Math.max(0, Math.round(position.x));
3969
+ scrollElement.scrollTop = Math.max(0, Math.round(position.y));
3970
+ return;
3971
+ }
3972
+ environment.window.scrollTo(
3973
+ Math.max(0, Math.round(position.x)),
3974
+ Math.max(0, Math.round(position.y))
3975
+ );
3976
+ }
3165
3977
 
3166
3978
  // src/react-shell/constants.ts
3167
3979
  var REVIEW_QA_FILTERS = [
@@ -3670,6 +4482,11 @@ var getSystemReviewTheme = () => {
3670
4482
 
3671
4483
  // src/react-shell/review/settings.modal.tsx
3672
4484
  var import_jsx_runtime2 = require("react/jsx-runtime");
4485
+ var getReviewThemeIcon = (theme) => {
4486
+ if (theme === "light") return Sun;
4487
+ if (theme === "system") return Monitor;
4488
+ return Moon;
4489
+ };
3673
4490
  var ReviewSettingsModal = ({
3674
4491
  figmaTokenDraft,
3675
4492
  reviewUserIdDraft,
@@ -3729,20 +4546,28 @@ var ReviewSettingsModal = ({
3729
4546
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "df-review-settings-body", children: [
3730
4547
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "df-review-settings-row", children: [
3731
4548
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Theme" }),
3732
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "df-review-settings-theme-options", children: REVIEW_THEME_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3733
- "button",
3734
- {
3735
- "aria-pressed": reviewThemeDraft === option.value,
3736
- className: `df-review-settings-theme-option${reviewThemeDraft === option.value ? " is-active" : ""}`,
3737
- type: "button",
3738
- onClick: () => {
3739
- onReviewThemeDraftChange(normalizeReviewTheme(option.value));
3740
- onClearStatus();
4549
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "df-review-settings-theme-options", children: REVIEW_THEME_OPTIONS.map((option) => {
4550
+ const ThemeIcon = getReviewThemeIcon(option.value);
4551
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
4552
+ "button",
4553
+ {
4554
+ "aria-pressed": reviewThemeDraft === option.value,
4555
+ className: `df-review-settings-theme-option${reviewThemeDraft === option.value ? " is-active" : ""}`,
4556
+ type: "button",
4557
+ onClick: () => {
4558
+ onReviewThemeDraftChange(
4559
+ normalizeReviewTheme(option.value)
4560
+ );
4561
+ onClearStatus();
4562
+ },
4563
+ children: [
4564
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ThemeIcon, { "aria-hidden": "true" }),
4565
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: option.label })
4566
+ ]
3741
4567
  },
3742
- children: option.label
3743
- },
3744
- option.value
3745
- )) })
4568
+ option.value
4569
+ );
4570
+ }) })
3746
4571
  ] }),
3747
4572
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "df-review-settings-field", children: [
3748
4573
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "df-review-settings-label-row", children: [
@@ -4593,7 +5418,7 @@ var getReviewItemRestoreScrollPosition = (targetWindow, targetDocument, item, an
4593
5418
  top: item.scroll?.y ?? 0
4594
5419
  });
4595
5420
  };
4596
- var setDocumentScrollInstantly = (targetWindow, targetDocument, position) => {
5421
+ var setDocumentScrollInstantly2 = (targetWindow, targetDocument, position) => {
4597
5422
  const scrollElement = targetDocument.scrollingElement;
4598
5423
  if (scrollElement) {
4599
5424
  scrollElement.scrollLeft = position.left;
@@ -4934,6 +5759,33 @@ var QaPanelHeader = ({
4934
5759
  }
4935
5760
  )
4936
5761
  ] }),
5762
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
5763
+ "select",
5764
+ {
5765
+ "aria-label": "QA status filter",
5766
+ className: "df-review-status-filter-select",
5767
+ value: qaStatusFilter,
5768
+ onChange: (event) => onQaStatusFilterChange(
5769
+ event.currentTarget.value
5770
+ ),
5771
+ children: [
5772
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "all", children: `All status (${qaStatusFilterCounts.get("all") ?? 0})` }),
5773
+ statusFilterOptions.map((statusOption) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: statusOption.value, children: `${statusOption.label} (${qaStatusFilterCounts.get(statusOption.value) ?? 0})` }, statusOption.value))
5774
+ ]
5775
+ }
5776
+ )
5777
+ ] }),
5778
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "df-review-list-title", children: [
5779
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "df-review-list-meta", children: [
5780
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: isAllQaVisible ? `${label} QA \xB7 All pages` : `${label} QA` }),
5781
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
5782
+ "strong",
5783
+ {
5784
+ title: `${activeRemainingItemCount} remaining of ${activeItemCount}`,
5785
+ children: !hasActiveFilter ? `${activeRemainingItemCount}/${activeItemCount}` : `${filteredItemCount}/${activeItemCount}`
5786
+ }
5787
+ )
5788
+ ] }),
4937
5789
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "df-review-filter-tabs", "aria-label": "QA filters", children: REVIEW_QA_FILTERS.map((filter) => {
4938
5790
  const count = qaFilterCounts.get(filter.key) ?? 0;
4939
5791
  const isActive = qaFilter === filter.key;
@@ -4950,25 +5802,6 @@ var QaPanelHeader = ({
4950
5802
  filter.key
4951
5803
  );
4952
5804
  }) })
4953
- ] }),
4954
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "df-review-list-title", children: [
4955
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { children: isAllQaVisible ? `${label} QA \xB7 All pages` : `${label} QA` }),
4956
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { title: `${activeRemainingItemCount} remaining of ${activeItemCount}`, children: !hasActiveFilter ? `${activeRemainingItemCount}/${activeItemCount}` : `${filteredItemCount}/${activeItemCount}` }),
4957
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
4958
- "select",
4959
- {
4960
- "aria-label": "QA status filter",
4961
- className: "df-review-status-filter-select",
4962
- value: qaStatusFilter,
4963
- onChange: (event) => onQaStatusFilterChange(
4964
- event.currentTarget.value
4965
- ),
4966
- children: [
4967
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "all", children: `All status (${qaStatusFilterCounts.get("all") ?? 0})` }),
4968
- statusFilterOptions.map((statusOption) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: statusOption.value, children: `${statusOption.label} (${qaStatusFilterCounts.get(statusOption.value) ?? 0})` }, statusOption.value))
4969
- ]
4970
- }
4971
- )
4972
5805
  ] })
4973
5806
  ] });
4974
5807
  };
@@ -5094,7 +5927,6 @@ var ReviewQaPanel = ({
5094
5927
  // src/react-shell/presence/overlay.tsx
5095
5928
  var import_react6 = require("react");
5096
5929
  var import_jsx_runtime11 = require("react/jsx-runtime");
5097
- var COLLAPSED_USER_COUNT = 1;
5098
5930
  var getPresenceName = (user) => user.displayName || user.userId;
5099
5931
  var PresenceOverlay = ({
5100
5932
  presenceSessionId,
@@ -5102,18 +5934,31 @@ var PresenceOverlay = ({
5102
5934
  }) => {
5103
5935
  const [isExpanded, setIsExpanded] = (0, import_react6.useState)(false);
5104
5936
  if (users.length === 0) return null;
5105
- const visibleUsers = isExpanded ? users : users.slice(0, COLLAPSED_USER_COUNT);
5106
- const hiddenUserCount = users.length - visibleUsers.length;
5107
5937
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
5108
5938
  "div",
5109
5939
  {
5110
5940
  "aria-label": `Review presence, ${users.length} online`,
5111
5941
  className: `df-review-presence-overlay${isExpanded ? " is-expanded" : ""}`,
5112
5942
  children: [
5113
- visibleUsers.map((user) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5943
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
5944
+ "button",
5945
+ {
5946
+ "aria-label": `Show online reviewers, ${users.length} online`,
5947
+ "aria-expanded": isExpanded,
5948
+ className: "df-review-presence-button",
5949
+ type: "button",
5950
+ onClick: () => setIsExpanded((current) => !current),
5951
+ children: [
5952
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(UserRound, { "aria-hidden": "true" }),
5953
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "df-review-presence-badge", children: users.length })
5954
+ ]
5955
+ }
5956
+ ),
5957
+ isExpanded && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "df-review-presence-list", role: "list", children: users.map((user) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
5114
5958
  "span",
5115
5959
  {
5116
5960
  className: `df-review-presence-chip${user.sessionId === presenceSessionId ? " is-self" : ""}`,
5961
+ role: "listitem",
5117
5962
  style: {
5118
5963
  "--df-review-presence-color": user.color
5119
5964
  },
@@ -5121,21 +5966,7 @@ var PresenceOverlay = ({
5121
5966
  children: getPresenceName(user)
5122
5967
  },
5123
5968
  user.sessionId
5124
- )),
5125
- hiddenUserCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
5126
- "button",
5127
- {
5128
- "aria-label": `Show ${hiddenUserCount} more online reviewers`,
5129
- "aria-expanded": isExpanded,
5130
- className: "df-review-presence-more",
5131
- type: "button",
5132
- onClick: () => setIsExpanded(true),
5133
- children: [
5134
- "+",
5135
- hiddenUserCount
5136
- ]
5137
- }
5138
- )
5969
+ )) })
5139
5970
  ]
5140
5971
  }
5141
5972
  );
@@ -5152,28 +5983,173 @@ var SOURCE_SELECTOR = [
5152
5983
  "[data-section-index]",
5153
5984
  "[data-section-id]"
5154
5985
  ].join(", ");
5155
- var getSourceCandidates = (target) => {
5986
+ var SECTION_OUTLINE_ROOT_SELECTOR = [
5987
+ "[data-wrk-source-component]",
5988
+ "header[data-wrk-source-file]",
5989
+ "footer[data-wrk-source-file]",
5990
+ '[role="banner"][data-wrk-source-file]',
5991
+ '[role="contentinfo"][data-wrk-source-file]'
5992
+ ].join(", ");
5993
+ var matchesIgnore = (file, patterns) => {
5994
+ const normalized = file.replace(/\\/g, "/");
5995
+ return patterns.some(
5996
+ (pattern) => typeof pattern === "string" ? normalized.includes(pattern) : pattern.test(normalized)
5997
+ );
5998
+ };
5999
+ var getSourceCandidates = (target, options) => {
5156
6000
  const startElement = getEventElement(target);
5157
6001
  if (!startElement) return [];
5158
6002
  const candidates = [];
5159
6003
  const seen = /* @__PURE__ */ new Set();
6004
+ const add = (element2, source, depth2, kind) => {
6005
+ if (!source?.file) return;
6006
+ const key = getSourceFileCompareKey(source.file);
6007
+ if (!addSourceFileCompareKey(seen, key)) return;
6008
+ candidates.push(createSourceCandidate(element2, source, depth2, kind));
6009
+ };
5160
6010
  let element = startElement;
5161
6011
  let depth = 0;
5162
6012
  while (element && element.nodeType === 1) {
5163
- const source = getSourceHintFromElement(element);
5164
- if (source?.file) {
5165
- const key = getSourceCandidateKey(source);
5166
- if (!seen.has(key)) {
5167
- seen.add(key);
5168
- candidates.push(createSourceCandidate(element, source, depth));
5169
- }
5170
- }
6013
+ add(element, getSourceHintFromElement(element), depth, "source");
6014
+ add(element, getDataHintFromElement(element), 0, "data");
5171
6015
  if (element === element.ownerDocument.documentElement) break;
5172
6016
  element = element.parentElement;
5173
6017
  depth += 1;
5174
6018
  }
5175
- return candidates.slice(0, 8);
6019
+ const ignore = options?.ignore;
6020
+ const visible = candidates.filter(
6021
+ (candidate) => !isCoreOutlineNode(candidate.label, candidate.source.file) && !(ignore?.length && matchesIgnore(candidate.source.file ?? "", ignore))
6022
+ );
6023
+ return visible.slice(0, 8);
6024
+ };
6025
+ var getSectionOutline = (root, options) => {
6026
+ const maxDepth = options?.maxDepth ?? 9;
6027
+ return getSectionOutlineRoots(root, options).map((element, index) => {
6028
+ const source = getSourceHintFromElement(element);
6029
+ const label = getOutlineLabel(element, source, "section");
6030
+ const seen = /* @__PURE__ */ new Set();
6031
+ if (source?.file) {
6032
+ addSourceFileCompareKey(seen, getOutlineSourceKey(source));
6033
+ }
6034
+ return {
6035
+ id: `${label}-${index}`,
6036
+ label,
6037
+ depth: 1,
6038
+ filePath: getDisplaySourcePath(source?.file) ?? label,
6039
+ element,
6040
+ source,
6041
+ data: getDataHintFromElement(element),
6042
+ children: getSectionOutlineChildren(
6043
+ element,
6044
+ 2,
6045
+ maxDepth,
6046
+ seen,
6047
+ options
6048
+ )
6049
+ };
6050
+ });
5176
6051
  };
6052
+ function getSectionOutlineRoots(root, options) {
6053
+ return Array.from(root.querySelectorAll(SECTION_OUTLINE_ROOT_SELECTOR)).filter(
6054
+ (element) => {
6055
+ const source = getSourceHintFromElement(element);
6056
+ const label = getOutlineLabel(element, source, "");
6057
+ return !isSkippedOutlineNode(label, source?.file, options);
6058
+ }
6059
+ );
6060
+ }
6061
+ function getSectionOutlineChildren(parent, depth, maxDepth, seen, options) {
6062
+ if (depth > maxDepth) return [];
6063
+ const entries = [];
6064
+ for (const child of Array.from(parent.children)) {
6065
+ const source = getSourceHintFromElement(child);
6066
+ const label = getOutlineLabel(child, source, child.tagName.toLowerCase());
6067
+ const sourceKey = source?.file ? getOutlineSourceKey(source) : "";
6068
+ const isNewSource = Boolean(sourceKey) && !hasEquivalentSourceFileKey(seen, sourceKey);
6069
+ if (shouldStopOutlineBranch(label, source?.file, options)) continue;
6070
+ if (isHiddenOutlineNode(label, source?.file, options)) {
6071
+ entries.push(
6072
+ ...getSectionOutlineChildren(child, depth, maxDepth, seen, options)
6073
+ );
6074
+ continue;
6075
+ }
6076
+ if (source?.file && isNewSource) {
6077
+ const childSeen = new Set(seen);
6078
+ addSourceFileCompareKey(childSeen, sourceKey);
6079
+ entries.push({
6080
+ id: `${sourceKey}-${getElementOutlinePath(child)}-${entries.length}`,
6081
+ label,
6082
+ depth,
6083
+ filePath: getDisplaySourcePath(source.file) ?? source.file,
6084
+ element: child,
6085
+ source,
6086
+ data: getDataHintFromElement(child),
6087
+ children: getSectionOutlineChildren(
6088
+ child,
6089
+ depth + 1,
6090
+ maxDepth,
6091
+ childSeen,
6092
+ options
6093
+ )
6094
+ });
6095
+ continue;
6096
+ }
6097
+ entries.push(
6098
+ ...getSectionOutlineChildren(child, depth, maxDepth, seen, options)
6099
+ );
6100
+ }
6101
+ return entries;
6102
+ }
6103
+ function getElementOutlinePath(element) {
6104
+ const indices = [];
6105
+ let current = element;
6106
+ while (current?.parentElement) {
6107
+ indices.unshift(Array.from(current.parentElement.children).indexOf(current));
6108
+ current = current.parentElement;
6109
+ }
6110
+ return indices.join("-");
6111
+ }
6112
+ function getOutlineSourceKey(source) {
6113
+ return getSourceFileCompareKey(source.file);
6114
+ }
6115
+ function getSourceFileCompareKey(file) {
6116
+ return file?.trim().replace(/\\/g, "/") ?? "";
6117
+ }
6118
+ function addSourceFileCompareKey(seen, key) {
6119
+ if (!key || hasEquivalentSourceFileKey(seen, key)) return false;
6120
+ seen.add(key);
6121
+ return true;
6122
+ }
6123
+ function hasEquivalentSourceFileKey(seen, key) {
6124
+ for (const seenKey of seen) {
6125
+ if (isEquivalentSourceFileKey(seenKey, key)) return true;
6126
+ }
6127
+ return false;
6128
+ }
6129
+ function isEquivalentSourceFileKey(a, b) {
6130
+ return a === b || a.endsWith(`/${b}`) || b.endsWith(`/${a}`);
6131
+ }
6132
+ function getOutlineLabel(element, source, fallback) {
6133
+ return source?.component?.trim() || getComponentNameFromSourceFile(source?.file) || element.id.trim() || fallback;
6134
+ }
6135
+ function isHiddenOutlineNode(label, file, options) {
6136
+ const ignore = options?.ignore;
6137
+ const isIgnoredSource = file && ignore?.length ? matchesIgnore(file, ignore) : false;
6138
+ return isCoreOutlineNode(label, file) || isIgnoredSource;
6139
+ }
6140
+ function shouldStopOutlineBranch(label, file, options) {
6141
+ return !options?.includePlacer && isPlacerOutlineNode(label, file);
6142
+ }
6143
+ function isSkippedOutlineNode(label, file, options) {
6144
+ return shouldStopOutlineBranch(label, file, options) || isHiddenOutlineNode(label, file, options);
6145
+ }
6146
+ function isCoreOutlineNode(label, file) {
6147
+ const text = `${label} ${file ?? ""}`.toLowerCase();
6148
+ return text.includes("core.section") || text.includes("core.content") || text.includes("core.column") || ["coresection", "corecontent", "corecolumn"].includes(label.toLowerCase());
6149
+ }
6150
+ function isPlacerOutlineNode(label, file) {
6151
+ return `${label} ${file ?? ""}`.toLowerCase().includes("placer");
6152
+ }
5177
6153
  var getSourceOpenUrl = (source, options) => {
5178
6154
  const normalizedOptions = normalizeSourceOpenOptions(options);
5179
6155
  const file = source?.file?.trim();
@@ -5212,6 +6188,18 @@ var openSourceInEditor = (source, options) => {
5212
6188
  window.open(url, "_blank", "noreferrer");
5213
6189
  return true;
5214
6190
  };
6191
+ function getDataHintFromElement(element) {
6192
+ const file = getSourceAttribute(element, "data-wrk-data-file");
6193
+ if (!file) return void 0;
6194
+ return {
6195
+ component: void 0,
6196
+ file,
6197
+ line: getSourceAttribute(element, "data-wrk-data-line"),
6198
+ column: void 0,
6199
+ sectionId: void 0,
6200
+ sectionIndex: void 0
6201
+ };
6202
+ }
5215
6203
  function getSourceHintFromElement(element) {
5216
6204
  const source = {
5217
6205
  component: getSourceAttribute(
@@ -5227,7 +6215,7 @@ function getSourceHintFromElement(element) {
5227
6215
  };
5228
6216
  return Object.values(source).some(Boolean) ? source : void 0;
5229
6217
  }
5230
- function createSourceCandidate(element, source, depth) {
6218
+ function createSourceCandidate(element, source, depth, kind) {
5231
6219
  const confidence = getSourceConfidence(source, depth);
5232
6220
  const fileName = source.file?.split(/[\\/]/).pop() ?? source.file ?? "source";
5233
6221
  const component = source.component?.trim();
@@ -5237,7 +6225,7 @@ function createSourceCandidate(element, source, depth) {
5237
6225
  const column = getSourcePosition(source.column);
5238
6226
  const position = line ? `:${line}${column ? `:${column}` : ""}` : "";
5239
6227
  return {
5240
- id: getSourceCandidateKey(source),
6228
+ id: `${kind}:${getSourceCandidateKey(source)}`,
5241
6229
  depth,
5242
6230
  element,
5243
6231
  filePath: getDisplaySourcePath(source.file) ?? fileName,
@@ -5246,6 +6234,7 @@ function createSourceCandidate(element, source, depth) {
5246
6234
  positionLabel: line ? `${line}:${column ?? 1}` : "",
5247
6235
  confidence,
5248
6236
  confidenceLabel: confidence >= 0.82 ? "high" : confidence >= 0.58 ? "medium" : "low",
6237
+ kind,
5249
6238
  usesPosition: confidence >= 0.72 && Boolean(line),
5250
6239
  source
5251
6240
  };
@@ -5269,7 +6258,7 @@ function getDisplaySourcePath(file) {
5269
6258
  const normalizedFile = file?.trim().replace(/\\/g, "/");
5270
6259
  if (!normalizedFile) return void 0;
5271
6260
  const sourceRootMatch = normalizedFile.match(
5272
- /(?:^|\/)((?:dev\/)?src\/.+|app\/.+|pages\/.+|components\/.+)$/
6261
+ /(?:^|\/)((?:dev\/)?src\/.+|app\/.+|pages?\/.+|components\/.+)$/
5273
6262
  );
5274
6263
  return sourceRootMatch?.[1] ?? normalizedFile;
5275
6264
  }
@@ -5653,7 +6642,98 @@ var FigmaIcon = () => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5653
6642
  xmlns: "http://www.w3.org/2000/svg",
5654
6643
  children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("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" })
5655
6644
  }
5656
- );
6645
+ );
6646
+
6647
+ // src/react-shell/target/target.ts
6648
+ var HIDE_SCROLLBAR_STYLE_ID = "df-review-hide-scrollbar";
6649
+ var FIGMA_POINTER_LOCK_STYLE_ID = "df-review-figma-pointer-lock";
6650
+ var setTargetScrollbarHidden = (targetDocument, hidden) => {
6651
+ if (!targetDocument) return;
6652
+ const existing = targetDocument.getElementById(HIDE_SCROLLBAR_STYLE_ID);
6653
+ if (hidden) {
6654
+ if (existing) return;
6655
+ const style = targetDocument.createElement("style");
6656
+ style.id = HIDE_SCROLLBAR_STYLE_ID;
6657
+ style.textContent = "html{scrollbar-width:none}html::-webkit-scrollbar,body::-webkit-scrollbar{width:0;height:0;display:none}";
6658
+ targetDocument.head?.appendChild(style);
6659
+ } else {
6660
+ existing?.remove();
6661
+ }
6662
+ };
6663
+ var setTargetFigmaOverlayLocked = (targetDocument, locked) => {
6664
+ if (!targetDocument) return;
6665
+ const existing = targetDocument.getElementById(FIGMA_POINTER_LOCK_STYLE_ID);
6666
+ if (locked) {
6667
+ if (existing) return;
6668
+ const style = targetDocument.createElement("style");
6669
+ style.id = FIGMA_POINTER_LOCK_STYLE_ID;
6670
+ style.textContent = [
6671
+ ".helper-figma-root,",
6672
+ ".helper-figma-root *,",
6673
+ ".helper-figma-loading-backdrop,",
6674
+ ".helper-figma-loading-backdrop * {",
6675
+ "pointer-events: none !important;",
6676
+ "}"
6677
+ ].join("\n");
6678
+ targetDocument.head?.appendChild(style);
6679
+ } else {
6680
+ existing?.remove();
6681
+ }
6682
+ };
6683
+ var isEditableEventTarget = (event) => {
6684
+ const path = event.composedPath?.() ?? [];
6685
+ const element = path[0] ?? event.target;
6686
+ if (!element || typeof element.tagName !== "string") return false;
6687
+ const tag = element.tagName;
6688
+ return tag === "INPUT" || tag === "TEXTAREA" || element.isContentEditable === true;
6689
+ };
6690
+ var TRUE_STORAGE_VALUES = /* @__PURE__ */ new Set([
6691
+ "1",
6692
+ "true",
6693
+ "on",
6694
+ "show",
6695
+ "shown",
6696
+ "visible",
6697
+ "enabled",
6698
+ "yes"
6699
+ ]);
6700
+ var OVERLAY_STORAGE_KEYS = {
6701
+ grid: ["isHelp", "df-review-grid-overlay", "dfReviewGridOverlay"],
6702
+ figma: ["isFigmaHelp", "df-review-figma-overlay", "dfReviewFigmaOverlay"]
6703
+ };
6704
+ var isStoredOverlayEnabled = (value) => TRUE_STORAGE_VALUES.has(value?.trim().toLowerCase() ?? "");
6705
+ var getCookieValue = (targetDocument, name) => {
6706
+ const cookies = targetDocument?.cookie ? targetDocument.cookie.split(";") : [];
6707
+ const prefix = `${name}=`;
6708
+ const match = cookies.map((cookie) => cookie.trim()).find((cookie) => cookie.startsWith(prefix));
6709
+ return match ? decodeURIComponent(match.slice(prefix.length)) : null;
6710
+ };
6711
+ var getStorageValue = (storage, key) => {
6712
+ try {
6713
+ return storage?.getItem(key) ?? null;
6714
+ } catch {
6715
+ return null;
6716
+ }
6717
+ };
6718
+ var getStoredOverlayState = (targetDocument, overlay) => {
6719
+ const targetWindow = targetDocument?.defaultView;
6720
+ return OVERLAY_STORAGE_KEYS[overlay].some((key) => {
6721
+ if (isStoredOverlayEnabled(getCookieValue(targetDocument, key))) {
6722
+ return true;
6723
+ }
6724
+ return isStoredOverlayEnabled(getStorageValue(targetWindow?.localStorage, key)) || isStoredOverlayEnabled(getStorageValue(targetWindow?.sessionStorage, key));
6725
+ });
6726
+ };
6727
+ var getTargetOverlayState = (targetDocument) => ({
6728
+ grid: Boolean(
6729
+ targetDocument?.body.classList.contains("is-help") || targetDocument?.querySelector(".helper.onShow") || getStoredOverlayState(targetDocument, "grid")
6730
+ ),
6731
+ figma: Boolean(
6732
+ targetDocument?.querySelector(
6733
+ ".helper-figma-root, .helper-figma-loading-backdrop"
6734
+ ) || getStoredOverlayState(targetDocument, "figma")
6735
+ )
6736
+ });
5657
6737
 
5658
6738
  // src/react-shell/topbar.tsx
5659
6739
  var import_jsx_runtime16 = require("react/jsx-runtime");
@@ -5669,6 +6749,7 @@ var ViewportPresetIcon = ({
5669
6749
  }) => {
5670
6750
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ReviewScopeIcon2, { scope: getViewportPresetKind(preset) });
5671
6751
  };
6752
+ var getPresetSelectValue = (preset) => `${preset.label}:${preset.width}x${preset.height}`;
5672
6753
  var ReviewTopbar = ({
5673
6754
  draftTarget,
5674
6755
  copyLabel,
@@ -5689,6 +6770,13 @@ var ReviewTopbar = ({
5689
6770
  onOpenInitialPrompt,
5690
6771
  onOpenSettings
5691
6772
  }) => {
6773
+ const selectedPresetValue = getPresetSelectValue(size);
6774
+ const handlePresetSelectChange = (event) => {
6775
+ const nextPreset = viewportPresets.find(
6776
+ (preset) => getPresetSelectValue(preset) === event.currentTarget.value
6777
+ );
6778
+ if (nextPreset) onSizeChange(nextPreset);
6779
+ };
5692
6780
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("header", { className: "df-review-topbar", children: [
5693
6781
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
5694
6782
  "form",
@@ -5738,6 +6826,27 @@ var ReviewTopbar = ({
5738
6826
  },
5739
6827
  preset.label
5740
6828
  )) }),
6829
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6830
+ "select",
6831
+ {
6832
+ "aria-label": "Viewport preset",
6833
+ className: "df-review-preset-select",
6834
+ value: selectedPresetValue,
6835
+ onChange: handlePresetSelectChange,
6836
+ children: viewportPresets.map((preset) => {
6837
+ const scope = getViewportPresetKind(preset);
6838
+ const count = presetScopeCounts.get(scope) ?? 0;
6839
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6840
+ "option",
6841
+ {
6842
+ value: getPresetSelectValue(preset),
6843
+ children: `${preset.label} (${count})`
6844
+ },
6845
+ getPresetSelectValue(preset)
6846
+ );
6847
+ })
6848
+ }
6849
+ ),
5741
6850
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "df-review-tool-divider", "aria-hidden": "true", children: "|" }),
5742
6851
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "df-review-active-size", children: [
5743
6852
  size.width,
@@ -5799,6 +6908,64 @@ var ReviewTopbar = ({
5799
6908
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Settings, { "aria-hidden": "true" })
5800
6909
  }
5801
6910
  )
6911
+ ] }),
6912
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("details", { className: "df-review-overlays-menu", children: [
6913
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("summary", { "aria-label": "Open target tools", title: "Tools", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Ellipsis, { "aria-hidden": "true" }) }),
6914
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "df-review-overlays-popover", "aria-label": "Target tools", children: [
6915
+ isRulerAvailable && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6916
+ "button",
6917
+ {
6918
+ "aria-label": "Toggle ruler",
6919
+ className: `df-review-overlay-button is-ruler${isRulerVisible ? " is-active" : ""}`,
6920
+ type: "button",
6921
+ onClick: onToggleRuler,
6922
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Ruler, { "aria-hidden": "true" })
6923
+ }
6924
+ ),
6925
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6926
+ "button",
6927
+ {
6928
+ "aria-label": "Toggle grid overlay",
6929
+ className: `df-review-overlay-button is-grid${targetOverlayState.grid ? " is-active" : ""}`,
6930
+ type: "button",
6931
+ onClick: () => onToggleTargetOverlay("grid"),
6932
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(LayoutGrid, { "aria-hidden": "true" })
6933
+ }
6934
+ ),
6935
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6936
+ "button",
6937
+ {
6938
+ "aria-disabled": !isFigmaOverlayAvailable,
6939
+ "aria-label": isFigmaOverlayAvailable ? "Toggle Figma overlay" : FIGMA_OVERLAY_UNAVAILABLE_MESSAGE,
6940
+ className: `df-review-overlay-button is-figma${targetOverlayState.figma ? " is-active" : ""}${isFigmaOverlayAvailable ? "" : " is-disabled"}`,
6941
+ disabled: !isFigmaOverlayAvailable,
6942
+ type: "button",
6943
+ onClick: () => onToggleTargetOverlay("figma"),
6944
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Image, { "aria-hidden": "true" })
6945
+ }
6946
+ ),
6947
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "df-review-tool-divider", "aria-hidden": "true", children: "|" }),
6948
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6949
+ "button",
6950
+ {
6951
+ "aria-label": "Open initial prompt",
6952
+ className: "df-review-overlay-button is-prompt",
6953
+ type: "button",
6954
+ onClick: onOpenInitialPrompt,
6955
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CircleQuestionMark, { "aria-hidden": "true" })
6956
+ }
6957
+ ),
6958
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
6959
+ "button",
6960
+ {
6961
+ "aria-label": "Open settings",
6962
+ className: "df-review-overlay-button is-settings",
6963
+ type: "button",
6964
+ onClick: onOpenSettings,
6965
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Settings, { "aria-hidden": "true" })
6966
+ }
6967
+ )
6968
+ ] })
5802
6969
  ] })
5803
6970
  ] })
5804
6971
  ] });
@@ -5809,7 +6976,7 @@ var import_react11 = require("react");
5809
6976
 
5810
6977
  // src/react-shell/hooks/use.review.item.restore.ts
5811
6978
  var import_react7 = require("react");
5812
- function runWithAutoScrollBehavior(targetDocument, callback) {
6979
+ function runWithAutoScrollBehavior2(targetDocument, callback) {
5813
6980
  const elements = [
5814
6981
  targetDocument?.documentElement,
5815
6982
  targetDocument?.body
@@ -5910,8 +7077,8 @@ var useReviewItemRestore = ({
5910
7077
  isCurrentRestore
5911
7078
  );
5912
7079
  if (!isCurrentRestore()) return false;
5913
- runWithAutoScrollBehavior(targetDocument, () => {
5914
- setDocumentScrollInstantly(
7080
+ runWithAutoScrollBehavior2(targetDocument, () => {
7081
+ setDocumentScrollInstantly2(
5915
7082
  targetWindow,
5916
7083
  targetDocument,
5917
7084
  getReviewItemRestoreScrollPosition(
@@ -6001,244 +7168,87 @@ function normalizeRoutePath(pathname) {
6001
7168
 
6002
7169
  // src/adapters/local.ts
6003
7170
  var DEFAULT_STORAGE_KEY = "df-web-review-kit:items";
6004
- function localAdapter(options = {}) {
6005
- const storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
6006
- const write = (items) => {
6007
- window.localStorage.setItem(storageKey, JSON.stringify(items));
6008
- };
6009
- const read = () => {
6010
- if (typeof window === "undefined") return [];
6011
- const raw = window.localStorage.getItem(storageKey);
6012
- if (!raw) return [];
6013
- try {
6014
- const value = JSON.parse(raw);
6015
- if (!Array.isArray(value)) return [];
6016
- let changed = false;
6017
- const items = value.flatMap((item) => {
6018
- const normalized = normalizeStoredReviewItem(item);
6019
- if (!normalized || normalized !== item) changed = true;
6020
- return normalized ? [normalized] : [];
6021
- });
6022
- if (changed) write(items);
6023
- return items;
6024
- } catch {
6025
- return [];
6026
- }
6027
- };
6028
- return {
6029
- async get(id) {
6030
- return read().find((item) => item.id === id) ?? null;
6031
- },
6032
- async list(query) {
6033
- return read().filter((item) => {
6034
- if (item.projectId !== query.projectId) return false;
6035
- const queryRouteKey = query.routeKey ?? query.normalizedPath;
6036
- if (queryRouteKey && getItemRouteKey(item) !== queryRouteKey) {
6037
- return false;
6038
- }
6039
- if (query.status && !matchesReviewItemStatus(item.status, query.status)) {
6040
- return false;
6041
- }
6042
- return true;
6043
- });
6044
- },
6045
- async create(item) {
6046
- const items = read();
6047
- items.unshift(item);
6048
- write(items);
6049
- return item;
6050
- },
6051
- async update(id, patch) {
6052
- const items = read();
6053
- const index = items.findIndex((item) => item.id === id);
6054
- if (index < 0) {
6055
- throw new Error(`Review item not found: ${id}`);
6056
- }
6057
- const next = {
6058
- ...items[index],
6059
- ...patch,
6060
- id,
6061
- createdAt: items[index].createdAt,
6062
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6063
- };
6064
- items[index] = next;
6065
- write(items);
6066
- return next;
6067
- },
6068
- async remove(id) {
6069
- write(read().filter((item) => item.id !== id));
6070
- }
6071
- };
6072
- }
6073
- function normalizeStoredReviewItem(value) {
6074
- if (!value || typeof value !== "object") return void 0;
6075
- const raw = value;
6076
- const kind = raw.kind === "text" ? "note" : raw.kind === "capture" ? "area" : raw.kind;
6077
- if (kind !== "note" && kind !== "area") return void 0;
6078
- const { screenshot: _screenshot, reviewNumber: _reviewNumber, ...item } = raw;
6079
- if (kind === raw.kind && _screenshot === void 0 && _reviewNumber === void 0) {
6080
- return raw;
6081
- }
6082
- return {
6083
- ...item,
6084
- kind
6085
- };
6086
- }
6087
-
6088
- // src/core/geometry.ts
6089
- function rectanglesIntersect(a, b) {
6090
- return a.left < b.left + b.width && a.left + a.width > b.left && a.top < b.top + b.height && a.top + a.height > b.top;
6091
- }
6092
- function getPointSelection(point) {
6093
- return {
6094
- left: point.x,
6095
- top: point.y,
6096
- width: 1,
6097
- height: 1
6098
- };
6099
- }
6100
- function toViewportSelection(selection) {
6101
- return {
6102
- left: selection.x,
6103
- top: selection.y,
6104
- width: selection.width,
6105
- height: selection.height
6106
- };
6107
- }
6108
- function toPublicSelection(selection) {
6109
- return {
6110
- x: Math.round(selection.left),
6111
- y: Math.round(selection.top),
6112
- width: Math.round(selection.width),
6113
- height: Math.round(selection.height)
6114
- };
6115
- }
6116
- function getSelectionCenter(selection) {
6117
- if ("left" in selection) {
6118
- return {
6119
- x: selection.left + selection.width / 2,
6120
- y: selection.top + selection.height / 2
6121
- };
6122
- }
6123
- return {
6124
- x: selection.x + selection.width / 2,
6125
- y: selection.y + selection.height / 2
6126
- };
6127
- }
6128
- function isRelativeSelection(value) {
6129
- if (!value || typeof value !== "object") return false;
6130
- const selection = value;
6131
- return typeof selection.x === "number" && typeof selection.y === "number" && typeof selection.width === "number" && typeof selection.height === "number";
6132
- }
6133
- function getViewportSize(environment) {
6134
- const targetWindow = environment?.window ?? window;
6135
- return {
6136
- width: targetWindow.innerWidth,
6137
- height: targetWindow.innerHeight
6138
- };
6139
- }
6140
- function isPointInViewport(point, environment) {
6141
- const viewport = getViewportSize(environment);
6142
- return point.x >= 0 && point.y >= 0 && point.x <= viewport.width && point.y <= viewport.height;
6143
- }
6144
- function isSelectionInViewport(selection, environment) {
6145
- const viewport = getViewportSize(environment);
6146
- return rectanglesIntersect(selection, {
6147
- left: 0,
6148
- top: 0,
6149
- width: viewport.width,
6150
- height: viewport.height
6151
- });
6152
- }
6153
- function clampPoint(point, environment) {
6154
- const viewport = getViewportSize(environment);
6155
- return {
6156
- x: clamp(point.x, 0, viewport.width),
6157
- y: clamp(point.y, 0, viewport.height)
6158
- };
6159
- }
6160
- function getPopoverPosition(point, environment, options) {
6161
- const bounds = getPopoverBounds(environment);
6162
- const margin = 12;
6163
- const width = Math.min(
6164
- options?.width ?? 320,
6165
- Math.max(240, bounds.width - margin * 2)
6166
- );
6167
- const estimatedHeight = options?.estimatedHeight ?? 178;
6168
- const offset = options?.offset ?? 12;
6169
- return {
6170
- left: clamp(
6171
- point.x + offset,
6172
- bounds.left + margin,
6173
- bounds.left + bounds.width - width - margin
6174
- ),
6175
- top: clamp(
6176
- point.y + offset,
6177
- bounds.top + margin,
6178
- bounds.top + bounds.height - estimatedHeight - margin
6179
- )
6180
- };
6181
- }
6182
- function getPopoverBounds(environment) {
6183
- if (!environment) {
6184
- return {
6185
- left: 0,
6186
- top: 0,
6187
- width: window.innerWidth,
6188
- height: window.innerHeight
6189
- };
6190
- }
6191
- return environment.overlayRect;
6192
- }
6193
- function toHostPoint(point, environment) {
6194
- if (!environment) return point;
6195
- return {
6196
- x: point.x + environment.viewportRect.left,
6197
- y: point.y + environment.viewportRect.top
7171
+ function localAdapter(options = {}) {
7172
+ const storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
7173
+ const write = (items) => {
7174
+ window.localStorage.setItem(storageKey, JSON.stringify(items));
6198
7175
  };
6199
- }
6200
- function toHostSelection(selection, environment) {
6201
- return {
6202
- left: selection.left + environment.viewportRect.left,
6203
- top: selection.top + environment.viewportRect.top,
6204
- width: selection.width,
6205
- height: selection.height
7176
+ const read = () => {
7177
+ if (typeof window === "undefined") return [];
7178
+ const raw = window.localStorage.getItem(storageKey);
7179
+ if (!raw) return [];
7180
+ try {
7181
+ const value = JSON.parse(raw);
7182
+ if (!Array.isArray(value)) return [];
7183
+ let changed = false;
7184
+ const items = value.flatMap((item) => {
7185
+ const normalized = normalizeStoredReviewItem(item);
7186
+ if (!normalized || normalized !== item) changed = true;
7187
+ return normalized ? [normalized] : [];
7188
+ });
7189
+ if (changed) write(items);
7190
+ return items;
7191
+ } catch {
7192
+ return [];
7193
+ }
6206
7194
  };
6207
- }
6208
- function toTargetPoint(point, environment) {
6209
- if (!environment) return point;
6210
7195
  return {
6211
- x: point.x - environment.viewportRect.left,
6212
- y: point.y - environment.viewportRect.top
6213
- };
6214
- }
6215
- function toTargetPointFromHostEvent(event, environment) {
6216
- return toTargetPoint(
6217
- {
6218
- x: event.clientX,
6219
- y: event.clientY
7196
+ async get(id) {
7197
+ return read().find((item) => item.id === id) ?? null;
6220
7198
  },
6221
- environment
6222
- );
6223
- }
6224
- function placeLayerOverTarget(layer, environment) {
6225
- layer.style.left = `${environment.viewportRect.left}px`;
6226
- layer.style.top = `${environment.viewportRect.top}px`;
6227
- layer.style.width = `${environment.viewportRect.width}px`;
6228
- layer.style.height = `${environment.viewportRect.height}px`;
6229
- layer.style.right = "auto";
6230
- layer.style.bottom = "auto";
6231
- }
6232
- function clamp(value, min, max) {
6233
- return Math.min(Math.max(value, min), Math.max(min, max));
6234
- }
6235
- function roundRatio(value) {
6236
- return Math.round(value * 1e4) / 1e4;
7199
+ async list(query) {
7200
+ return read().filter((item) => {
7201
+ if (item.projectId !== query.projectId) return false;
7202
+ const queryRouteKey = query.routeKey ?? query.normalizedPath;
7203
+ if (queryRouteKey && getItemRouteKey(item) !== queryRouteKey) {
7204
+ return false;
7205
+ }
7206
+ if (query.status && !matchesReviewItemStatus(item.status, query.status)) {
7207
+ return false;
7208
+ }
7209
+ return true;
7210
+ });
7211
+ },
7212
+ async create(item) {
7213
+ const items = read();
7214
+ items.unshift(item);
7215
+ write(items);
7216
+ return item;
7217
+ },
7218
+ async update(id, patch) {
7219
+ const items = read();
7220
+ const index = items.findIndex((item) => item.id === id);
7221
+ if (index < 0) {
7222
+ throw new Error(`Review item not found: ${id}`);
7223
+ }
7224
+ const next = {
7225
+ ...items[index],
7226
+ ...patch,
7227
+ id,
7228
+ createdAt: items[index].createdAt,
7229
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
7230
+ };
7231
+ items[index] = next;
7232
+ write(items);
7233
+ return next;
7234
+ },
7235
+ async remove(id) {
7236
+ write(read().filter((item) => item.id !== id));
7237
+ }
7238
+ };
6237
7239
  }
6238
- function roundPoint(point) {
7240
+ function normalizeStoredReviewItem(value) {
7241
+ if (!value || typeof value !== "object") return void 0;
7242
+ const raw = value;
7243
+ const kind = raw.kind === "text" ? "note" : raw.kind === "capture" ? "area" : raw.kind;
7244
+ if (kind !== "note" && kind !== "area") return void 0;
7245
+ const { screenshot: _screenshot, reviewNumber: _reviewNumber, ...item } = raw;
7246
+ if (kind === raw.kind && _screenshot === void 0 && _reviewNumber === void 0) {
7247
+ return raw;
7248
+ }
6239
7249
  return {
6240
- x: Math.round(point.x),
6241
- y: Math.round(point.y)
7250
+ ...item,
7251
+ kind
6242
7252
  };
6243
7253
  }
6244
7254
 
@@ -6278,8 +7288,22 @@ function getDomAnchorFromPoint(point, configuredAttribute = "data-qa-id", enviro
6278
7288
  source: getDomSourceHint(target)
6279
7289
  };
6280
7290
  }
6281
- function getElementViewportSelection(anchor, environment) {
6282
- const element = getAnchorElement(anchor, environment);
7291
+ function getDomAnchorFromElement(target, configuredAttribute = "data-qa-id", environment) {
7292
+ if (target.ownerDocument !== environment.document) return void 0;
7293
+ const candidates = createAnchorCandidates(target, configuredAttribute);
7294
+ const primary = candidates[0];
7295
+ if (!primary) return void 0;
7296
+ return {
7297
+ ...primary,
7298
+ candidates,
7299
+ htmlSnippet: getElementHtmlSnippet(
7300
+ getAnchorSourceElement(target, primary, configuredAttribute) ?? target
7301
+ ),
7302
+ source: getDomSourceHint(target)
7303
+ };
7304
+ }
7305
+ function getElementViewportSelection(anchor, environment, preferredSelection) {
7306
+ const element = getAnchorElement(anchor, environment, preferredSelection);
6283
7307
  if (!element) return void 0;
6284
7308
  const rect = element.getBoundingClientRect();
6285
7309
  if (rect.width <= 0 || rect.height <= 0) return void 0;
@@ -6318,22 +7342,35 @@ function getAnchorCandidates(anchor) {
6318
7342
  ...anchor.candidates ?? []
6319
7343
  ]);
6320
7344
  }
6321
- function resolveAnchorElement(anchor, environment) {
7345
+ function resolveAnchorElement(anchor, environment, preferredSelection) {
6322
7346
  const matches = getAnchorCandidates(anchor).flatMap((candidate) => {
6323
- const match = queryBestAnchorCandidate(
6324
- candidate,
6325
- candidate.textFingerprint ?? anchor.textFingerprint,
6326
- environment
6327
- );
6328
- if (!match) return [];
6329
- const confidence = roundRatio(
6330
- (candidate.confidence ?? 0.5) * match.score
6331
- );
6332
- return [{
6333
- element: match.element,
6334
- candidate,
6335
- confidence
6336
- }];
7347
+ const textFingerprint = candidate.textFingerprint ?? anchor.textFingerprint;
7348
+ if (!preferredSelection) {
7349
+ const match = queryBestAnchorCandidate(
7350
+ candidate,
7351
+ textFingerprint,
7352
+ environment
7353
+ );
7354
+ if (!match) return [];
7355
+ const confidence = roundRatio(
7356
+ (candidate.confidence ?? 0.5) * match.score
7357
+ );
7358
+ return [{
7359
+ element: match.element,
7360
+ candidate,
7361
+ confidence
7362
+ }];
7363
+ }
7364
+ return queryAnchorElements(candidate.selector, environment).map((element) => {
7365
+ const confidence = roundRatio(
7366
+ (candidate.confidence ?? 0.5) * getTextFingerprintScore(textFingerprint, getTextFingerprint(element)) * getSelectionMatchScore(element, preferredSelection)
7367
+ );
7368
+ return {
7369
+ element,
7370
+ candidate,
7371
+ confidence
7372
+ };
7373
+ });
6337
7374
  });
6338
7375
  return matches.sort((a, b) => b.confidence - a.confidence)[0];
6339
7376
  }
@@ -6343,8 +7380,8 @@ function cssEscape(value) {
6343
7380
  }
6344
7381
  return value.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
6345
7382
  }
6346
- function getAnchorElement(anchor, environment) {
6347
- return typeof anchor === "string" ? queryAnchorElement(anchor, environment) : resolveAnchorElement(anchor, environment)?.element;
7383
+ function getAnchorElement(anchor, environment, preferredSelection) {
7384
+ return typeof anchor === "string" ? queryAnchorElement(anchor, environment) : resolveAnchorElement(anchor, environment, preferredSelection)?.element;
6348
7385
  }
6349
7386
  function createAnchorCandidates(target, configuredAttribute) {
6350
7387
  const targetCandidates = [];
@@ -6708,6 +7745,38 @@ function getTextFingerprintScore(expected, actual) {
6708
7745
  const matches = expectedTokens.filter((token) => actualTokens.has(token));
6709
7746
  return clamp(matches.length / expectedTokens.length, 0.25, 0.76);
6710
7747
  }
7748
+ function getSelectionMatchScore(element, selection) {
7749
+ const rect = element.getBoundingClientRect();
7750
+ if (rect.width <= 0 || rect.height <= 0) return 0.05;
7751
+ const overlapLeft = Math.max(rect.left, selection.left);
7752
+ const overlapTop = Math.max(rect.top, selection.top);
7753
+ const overlapRight = Math.min(rect.right, selection.left + selection.width);
7754
+ const overlapBottom = Math.min(rect.bottom, selection.top + selection.height);
7755
+ const overlapWidth = Math.max(0, overlapRight - overlapLeft);
7756
+ const overlapHeight = Math.max(0, overlapBottom - overlapTop);
7757
+ const overlapArea = overlapWidth * overlapHeight;
7758
+ if (overlapArea > 0) {
7759
+ const selectionArea = Math.max(1, selection.width * selection.height);
7760
+ const rectArea = Math.max(1, rect.width * rect.height);
7761
+ return 1 + overlapArea / Math.min(selectionArea, rectArea);
7762
+ }
7763
+ const rectCenterX = rect.left + rect.width / 2;
7764
+ const rectCenterY = rect.top + rect.height / 2;
7765
+ const selectionCenterX = selection.left + selection.width / 2;
7766
+ const selectionCenterY = selection.top + selection.height / 2;
7767
+ const distance = Math.hypot(
7768
+ rectCenterX - selectionCenterX,
7769
+ rectCenterY - selectionCenterY
7770
+ );
7771
+ const basis = Math.max(
7772
+ 1,
7773
+ rect.width,
7774
+ rect.height,
7775
+ selection.width,
7776
+ selection.height
7777
+ );
7778
+ return clamp(1 / (1 + distance / basis), 0.05, 0.95);
7779
+ }
6711
7780
  function getFingerprintTokens(value) {
6712
7781
  return value.toLowerCase().split(/[\s/|,.:;()[\]{}"'`~!?<>]+/).map((token) => token.trim()).filter((token) => token.length > 1);
6713
7782
  }
@@ -7069,42 +8138,6 @@ function isDomReviewItem(item) {
7069
8138
  return item.scope === "dom" || item.kind === "note" && Boolean(item.anchor && getItemSelection(item));
7070
8139
  }
7071
8140
 
7072
- // src/core/scroll.ts
7073
- function waitForNextFrame(environment) {
7074
- return new Promise((resolve) => {
7075
- (environment?.window ?? window).requestAnimationFrame(() => resolve());
7076
- });
7077
- }
7078
- function runWithAutoScrollBehavior2(targetDocument, callback) {
7079
- const elements = [
7080
- targetDocument.documentElement,
7081
- targetDocument.body
7082
- ].filter((element) => Boolean(element));
7083
- const previousValues = elements.map((element) => element.style.scrollBehavior);
7084
- elements.forEach((element) => {
7085
- element.style.scrollBehavior = "auto";
7086
- });
7087
- try {
7088
- callback();
7089
- } finally {
7090
- elements.forEach((element, index) => {
7091
- element.style.scrollBehavior = previousValues[index] ?? "";
7092
- });
7093
- }
7094
- }
7095
- function setDocumentScrollInstantly2(environment, position) {
7096
- const scrollElement = environment.document.scrollingElement;
7097
- if (scrollElement) {
7098
- scrollElement.scrollLeft = Math.max(0, Math.round(position.x));
7099
- scrollElement.scrollTop = Math.max(0, Math.round(position.y));
7100
- return;
7101
- }
7102
- environment.window.scrollTo(
7103
- Math.max(0, Math.round(position.x)),
7104
- Math.max(0, Math.round(position.y))
7105
- );
7106
- }
7107
-
7108
8141
  // src/core/overlay.style.ts
7109
8142
  function createStyleElement() {
7110
8143
  const style = document.createElement("style");
@@ -7765,6 +8798,12 @@ function createStyleElement() {
7765
8798
  outline-offset: 1px;
7766
8799
  }
7767
8800
 
8801
+ @media (hover: none) and (pointer: coarse) {
8802
+ .dfwr-textarea {
8803
+ font-size: 16px;
8804
+ }
8805
+ }
8806
+
7768
8807
  .dfwr-adjust-panel {
7769
8808
  display: grid;
7770
8809
  gap: 4px;
@@ -7838,32 +8877,6 @@ function createStyleElement() {
7838
8877
  pointer-events: none;
7839
8878
  }
7840
8879
 
7841
- .dfwr-adjust-hud {
7842
- position: fixed;
7843
- z-index: 5;
7844
- display: inline-flex;
7845
- align-items: center;
7846
- min-height: 22px;
7847
- padding: 0 8px;
7848
- border: 1px solid rgba(99, 215, 199, 0.72);
7849
- border-radius: var(--df-review-radius-sm);
7850
- background: rgba(21, 25, 29, 0.92);
7851
- box-shadow:
7852
- 0 0 0 3px rgba(99, 215, 199, 0.14),
7853
- 0 8px 18px rgba(0, 0, 0, 0.26);
7854
- color: #63d7c7;
7855
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
7856
- font-size: var(--df-review-font-size-2xs);
7857
- font-weight: 800;
7858
- line-height: 1;
7859
- pointer-events: none;
7860
- white-space: nowrap;
7861
- }
7862
-
7863
- .dfwr-adjust-hud[hidden] {
7864
- display: none;
7865
- }
7866
-
7867
8880
  .dfwr-empty,
7868
8881
  .dfwr-error {
7869
8882
  margin: 0;
@@ -8226,7 +9239,6 @@ var WebReviewKitView = class {
8226
9239
  if (event.button !== 0) return;
8227
9240
  cancel(event);
8228
9241
  });
8229
- layer.addEventListener("click", cancel);
8230
9242
  return layer;
8231
9243
  }
8232
9244
  getDraftAdjustmentMetrics(draft) {
@@ -8406,14 +9418,6 @@ var WebReviewKitView = class {
8406
9418
  if (!draft.selection) return void 0;
8407
9419
  return toViewportSelection(draft.selection.viewport);
8408
9420
  }
8409
- formatDraftAdjustmentStatus(draft) {
8410
- const metrics = this.getDraftAdjustmentMetrics(draft);
8411
- return [
8412
- `x ${this.formatSignedPx(metrics.x)}`,
8413
- `y ${this.formatSignedPx(metrics.y)}`,
8414
- `scale ${this.formatSignedPx(metrics.scale)}`
8415
- ].join(" / ");
8416
- }
8417
9421
  getDraftAdjustmentMetricLines(draft) {
8418
9422
  const metrics = this.getDraftAdjustmentMetrics(draft);
8419
9423
  return [
@@ -8425,6 +9429,7 @@ var WebReviewKitView = class {
8425
9429
  }
8426
9430
  withDraftAdjustmentComment(comment, draft) {
8427
9431
  if (!this.hasDraftAdjustment(draft)) return comment;
9432
+ const trimmedComment = comment.trim();
8428
9433
  const metrics = this.getDraftAdjustmentMetrics(draft);
8429
9434
  const adjustment = [
8430
9435
  `${this.getAdjustmentLabel()}: x ${this.formatSignedPx(
@@ -8436,12 +9441,20 @@ var WebReviewKitView = class {
8436
9441
  metrics.viewportWidth
8437
9442
  )}/design ${Math.round(metrics.designWidth)})`
8438
9443
  ].join(" ");
8439
- return `${comment.trim()}
8440
- ${adjustment}`;
9444
+ return trimmedComment ? `${trimmedComment}
9445
+ ${adjustment}` : adjustment;
8441
9446
  }
8442
9447
  getStyleableDraftElement(draft, environment) {
9448
+ if (draft.previewElement && draft.previewElement.ownerDocument === environment.document && "style" in draft.previewElement) {
9449
+ return draft.previewElement;
9450
+ }
8443
9451
  if (!draft.anchor) return void 0;
8444
- const element = resolveAnchorElement(draft.anchor, environment)?.element;
9452
+ const preferredSelection = draft.selection ? toViewportSelection(draft.selection.viewport) : void 0;
9453
+ const element = resolveAnchorElement(
9454
+ draft.anchor,
9455
+ environment,
9456
+ preferredSelection
9457
+ )?.element;
8445
9458
  if (!element) return void 0;
8446
9459
  if ("style" in element) return element;
8447
9460
  return void 0;
@@ -8461,39 +9474,77 @@ ${adjustment}`;
8461
9474
  this.restoreDraftPreview();
8462
9475
  }
8463
9476
  if (!this.draftPreview) {
8464
- const computedTransform = environment.window.getComputedStyle(element).transform;
9477
+ const computedStyle = environment.window.getComputedStyle(element);
9478
+ const clone = element.cloneNode(true);
9479
+ this.removeDuplicateIds(clone);
9480
+ this.copyComputedStyle(element, clone, environment);
9481
+ this.positionDraftPreviewClone(clone, element, computedStyle);
9482
+ environment.document.body?.appendChild(clone);
8465
9483
  this.draftPreview = {
8466
9484
  element,
8467
- transform: element.style.transform,
8468
- transformOrigin: element.style.transformOrigin,
8469
- transition: element.style.transition,
8470
- willChange: element.style.willChange,
8471
- baseTransform: element.style.transform || (computedTransform && computedTransform !== "none" ? computedTransform : "")
9485
+ clone,
9486
+ visibility: element.style.visibility
8472
9487
  };
9488
+ element.style.visibility = "hidden";
8473
9489
  }
8474
9490
  const metrics = this.getDraftAdjustmentMetrics(draft);
8475
9491
  const translate = `translate(${this.toCssNumber(metrics.cssX)}px, ${this.toCssNumber(
8476
9492
  metrics.cssY
8477
9493
  )}px)`;
8478
9494
  const scale = metrics.scaleFactor === 1 ? "" : `scale(${this.toCssNumber(metrics.scaleFactor)})`;
8479
- element.style.transition = "none";
8480
- element.style.willChange = "transform";
8481
- element.style.transformOrigin = "top left";
8482
- element.style.transform = [
8483
- this.draftPreview.baseTransform,
8484
- translate,
8485
- scale
8486
- ].filter(Boolean).join(" ");
9495
+ this.draftPreview.clone.style.transform = [translate, scale].filter(Boolean).join(" ");
8487
9496
  }
8488
9497
  restoreDraftPreview() {
8489
9498
  if (!this.draftPreview) return;
8490
- const { element, transform, transformOrigin, transition, willChange } = this.draftPreview;
8491
- element.style.transform = transform;
8492
- element.style.transformOrigin = transformOrigin;
8493
- element.style.transition = transition;
8494
- element.style.willChange = willChange;
9499
+ const { element, clone, visibility } = this.draftPreview;
9500
+ clone.remove();
9501
+ element.style.visibility = visibility;
8495
9502
  this.draftPreview = void 0;
8496
9503
  }
9504
+ positionDraftPreviewClone(clone, element, computedStyle) {
9505
+ const rect = element.getBoundingClientRect();
9506
+ clone.setAttribute("data-dfwr-adjust-preview", "true");
9507
+ clone.setAttribute("aria-hidden", "true");
9508
+ clone.style.position = "fixed";
9509
+ clone.style.left = `${this.toCssNumber(rect.left)}px`;
9510
+ clone.style.top = `${this.toCssNumber(rect.top)}px`;
9511
+ clone.style.right = "auto";
9512
+ clone.style.bottom = "auto";
9513
+ clone.style.width = `${this.toCssNumber(rect.width)}px`;
9514
+ clone.style.height = `${this.toCssNumber(rect.height)}px`;
9515
+ clone.style.maxWidth = "none";
9516
+ clone.style.maxHeight = "none";
9517
+ clone.style.margin = "0";
9518
+ clone.style.boxSizing = "border-box";
9519
+ clone.style.display = this.getDraftPreviewDisplay(computedStyle.display);
9520
+ clone.style.zIndex = "2147483646";
9521
+ clone.style.pointerEvents = "none";
9522
+ clone.style.transition = "none";
9523
+ clone.style.willChange = "transform";
9524
+ clone.style.transformOrigin = "top left";
9525
+ clone.style.transform = "none";
9526
+ }
9527
+ getDraftPreviewDisplay(display) {
9528
+ if (display === "inline" || display === "contents") return "inline-block";
9529
+ return display || "block";
9530
+ }
9531
+ copyComputedStyle(element, clone, environment) {
9532
+ const computedStyle = environment.window.getComputedStyle(element);
9533
+ for (let index = 0; index < computedStyle.length; index += 1) {
9534
+ const property = computedStyle.item(index);
9535
+ clone.style.setProperty(
9536
+ property,
9537
+ computedStyle.getPropertyValue(property),
9538
+ computedStyle.getPropertyPriority(property)
9539
+ );
9540
+ }
9541
+ }
9542
+ removeDuplicateIds(element) {
9543
+ element.removeAttribute("id");
9544
+ element.querySelectorAll("[id]").forEach((child) => {
9545
+ child.removeAttribute("id");
9546
+ });
9547
+ }
8497
9548
  toCssNumber(value) {
8498
9549
  return Math.round(value * 1e3) / 1e3;
8499
9550
  }
@@ -8655,8 +9706,8 @@ ${adjustment}`;
8655
9706
  });
8656
9707
  const saveDraft = () => {
8657
9708
  const comment = textarea.value.trim();
8658
- if (!comment) return;
8659
9709
  const currentDraft = this.state.noteDraft ?? draft;
9710
+ if (!comment && !this.hasDraftAdjustment(currentDraft)) return;
8660
9711
  void this.config.actions.createItem({
8661
9712
  kind: "note",
8662
9713
  comment: this.withDraftAdjustmentComment(comment, currentDraft),
@@ -8666,13 +9717,8 @@ ${adjustment}`;
8666
9717
  selection: currentDraft.selection
8667
9718
  });
8668
9719
  };
8669
- const adjustmentHud = isElementDraft ? this.createAdjustmentHud(draft, environment) : void 0;
8670
- if (adjustmentHud) {
8671
- group.append(adjustmentHud);
8672
- }
8673
9720
  const adjustmentControls = isElementDraft ? this.createAdjustmentControls({
8674
9721
  draft,
8675
- hud: adjustmentHud,
8676
9722
  pin,
8677
9723
  popover,
8678
9724
  selectionHighlight,
@@ -8800,7 +9846,6 @@ ${adjustment}`;
8800
9846
  }
8801
9847
  createAdjustmentControls({
8802
9848
  draft,
8803
- hud,
8804
9849
  pin,
8805
9850
  popover,
8806
9851
  selectionHighlight,
@@ -8838,7 +9883,6 @@ ${adjustment}`;
8838
9883
  scaleStatus.textContent = scaleLine;
8839
9884
  this.syncDraftAdjustmentUi({
8840
9885
  draft: nextDraft,
8841
- hud,
8842
9886
  pin,
8843
9887
  selectionHighlight
8844
9888
  });
@@ -8899,16 +9943,8 @@ ${adjustment}`;
8899
9943
  if (event.key.toLowerCase() === "s") return { x: 0, y: 0, scale: -step };
8900
9944
  return void 0;
8901
9945
  }
8902
- createAdjustmentHud(draft, environment) {
8903
- const hud = document.createElement("div");
8904
- hud.className = "dfwr-adjust-hud";
8905
- hud.setAttribute("aria-hidden", "true");
8906
- this.syncAdjustmentHud(hud, draft, environment);
8907
- return hud;
8908
- }
8909
9946
  syncDraftAdjustmentUi({
8910
9947
  draft,
8911
- hud,
8912
9948
  pin,
8913
9949
  selectionHighlight
8914
9950
  }) {
@@ -8933,26 +9969,8 @@ ${adjustment}`;
8933
9969
  selectionHighlight.style.width = `${rect.width}px`;
8934
9970
  selectionHighlight.style.height = `${rect.height}px`;
8935
9971
  }
8936
- if (hud) {
8937
- this.syncAdjustmentHud(hud, draft, environment);
8938
- }
8939
9972
  this.syncDraftPreview(draft);
8940
9973
  }
8941
- syncAdjustmentHud(hud, draft, environment) {
8942
- if (!draft.selection) return;
8943
- const rect = toHostSelection(
8944
- this.getAdjustedDraftSelection(
8945
- toViewportSelection(draft.selection.viewport),
8946
- draft
8947
- ),
8948
- environment
8949
- );
8950
- const isVisible = draft.adjustment?.isActive === true || this.hasDraftAdjustment(draft);
8951
- hud.hidden = !isVisible;
8952
- hud.textContent = this.formatDraftAdjustmentStatus(draft);
8953
- hud.style.left = `${Math.max(4, rect.left)}px`;
8954
- hud.style.top = `${Math.max(4, rect.top - 28)}px`;
8955
- }
8956
9974
  createAreaForm() {
8957
9975
  const form = document.createElement("form");
8958
9976
  form.className = "dfwr-form";
@@ -9076,12 +10094,18 @@ ${adjustment}`;
9076
10094
  save.className = "dfwr-button is-primary";
9077
10095
  save.type = "button";
9078
10096
  save.textContent = saveLabel;
9079
- save.addEventListener("click", onSave);
10097
+ save.addEventListener("click", (event) => {
10098
+ event.preventDefault();
10099
+ event.stopPropagation();
10100
+ onSave();
10101
+ });
9080
10102
  const cancel = document.createElement("button");
9081
10103
  cancel.className = "dfwr-button";
9082
10104
  cancel.type = "button";
9083
10105
  cancel.textContent = "Cancel";
9084
- cancel.addEventListener("click", () => {
10106
+ cancel.addEventListener("click", (event) => {
10107
+ event.preventDefault();
10108
+ event.stopPropagation();
9085
10109
  this.config.actions.setModeState("idle");
9086
10110
  this.config.actions.clearDrafts();
9087
10111
  this.config.actions.render();
@@ -9468,7 +10492,7 @@ ${formatItemMeta(item)}`;
9468
10492
 
9469
10493
  // src/core/web.review.kit.app.ts
9470
10494
  var ROOT_ID = "df-web-review-kit-root";
9471
- function isEditableEventTarget(event) {
10495
+ function isEditableEventTarget2(event) {
9472
10496
  const path = event.composedPath?.() ?? [];
9473
10497
  const element = path[0] ?? event.target;
9474
10498
  if (!element || typeof element.tagName !== "string") return false;
@@ -9486,6 +10510,7 @@ function createWebReviewKit(options) {
9486
10510
  close: () => app.close(),
9487
10511
  toggle: () => app.toggle(),
9488
10512
  setMode: (mode) => app.setMode(mode),
10513
+ startElementReview: (element, comment) => app.startElementReview(element, comment),
9489
10514
  getMode: () => app.getMode(),
9490
10515
  highlightItem: (itemId) => app.highlightItem(itemId),
9491
10516
  setHiddenItemIds: (itemIds) => app.setHiddenItemIds(itemIds),
@@ -9507,7 +10532,7 @@ var WebReviewKitApp = class {
9507
10532
  event.stopPropagation();
9508
10533
  return;
9509
10534
  }
9510
- if (isEditableEventTarget(event) || !isHotkey(event, this.hotkey)) return;
10535
+ if (isEditableEventTarget2(event) || !isHotkey(event, this.hotkey)) return;
9511
10536
  event.preventDefault();
9512
10537
  event.stopPropagation();
9513
10538
  this.toggle();
@@ -9616,6 +10641,16 @@ var WebReviewKitApp = class {
9616
10641
  this.areaDraft = void 0;
9617
10642
  this.render();
9618
10643
  }
10644
+ async startElementReview(element, comment) {
10645
+ if (!this.isOpen) {
10646
+ this.isOpen = true;
10647
+ }
10648
+ this.setModeState("element");
10649
+ this.noteDraft = void 0;
10650
+ this.areaDraft = void 0;
10651
+ this.isSelectingArea = false;
10652
+ await this.bindElementDraftToElement(element, comment);
10653
+ }
9619
10654
  getMode() {
9620
10655
  return this.mode;
9621
10656
  }
@@ -9779,13 +10814,26 @@ var WebReviewKitApp = class {
9779
10814
  const viewport = getViewportSize(environment);
9780
10815
  const nextPoint = clampPoint(point, environment);
9781
10816
  const draft = await this.withOverlayHidden(() => {
10817
+ const pointSelection = getPointSelection(nextPoint);
10818
+ const targetElement = environment.document.elementFromPoint(
10819
+ nextPoint.x,
10820
+ nextPoint.y
10821
+ );
10822
+ const previewElement = targetElement && "style" in targetElement ? targetElement : void 0;
10823
+ const targetRect = targetElement?.getBoundingClientRect();
10824
+ const clickedSelection = targetRect && targetRect.width > 0 && targetRect.height > 0 ? {
10825
+ left: targetRect.left,
10826
+ top: targetRect.top,
10827
+ width: targetRect.width,
10828
+ height: targetRect.height
10829
+ } : void 0;
9782
10830
  const anchor = getDomAnchorFromPoint(
9783
10831
  nextPoint,
9784
10832
  this.options.anchors?.attribute,
9785
10833
  environment
9786
10834
  );
9787
- const elementSelection = anchor ? getElementViewportSelection(anchor, environment) : void 0;
9788
- const selection = elementSelection ?? getPointSelection(nextPoint);
10835
+ const elementSelection = anchor ? clickedSelection ?? getElementViewportSelection(anchor, environment, pointSelection) : void 0;
10836
+ const selection = elementSelection ?? pointSelection;
9789
10837
  const markerPoint = elementSelection ? { x: selection.left, y: selection.top } : getSelectionCenter(selection);
9790
10838
  const reviewSelection = elementSelection ? {
9791
10839
  viewport: toPublicSelection(elementSelection),
@@ -9804,9 +10852,51 @@ var WebReviewKitApp = class {
9804
10852
  anchor,
9805
10853
  marker,
9806
10854
  selection: reviewSelection,
9807
- comment
10855
+ comment,
10856
+ previewElement
10857
+ };
10858
+ });
10859
+ this.noteDraft = draft;
10860
+ this.render();
10861
+ }
10862
+ async bindElementDraftToElement(element, comment) {
10863
+ const environment = this.getEnvironment();
10864
+ if (!environment || element.ownerDocument !== environment.document) return;
10865
+ const viewport = getViewportSize(environment);
10866
+ const draft = await this.withOverlayHidden(() => {
10867
+ const rect = element.getBoundingClientRect();
10868
+ if (rect.width <= 0 || rect.height <= 0) return void 0;
10869
+ const selection = {
10870
+ left: rect.left,
10871
+ top: rect.top,
10872
+ width: rect.width,
10873
+ height: rect.height
10874
+ };
10875
+ const anchor = getDomAnchorFromElement(
10876
+ element,
10877
+ this.options.anchors?.attribute,
10878
+ environment
10879
+ );
10880
+ const markerPoint = { x: selection.left, y: selection.top };
10881
+ const marker = {
10882
+ viewport: roundPoint(markerPoint),
10883
+ relative: anchor ? getRelativePoint(markerPoint, anchor, environment) : void 0
10884
+ };
10885
+ const reviewSelection = {
10886
+ viewport: toPublicSelection(selection),
10887
+ relative: anchor ? getRelativeSelection(selection, anchor, environment) : void 0
10888
+ };
10889
+ const previewElement = "style" in element ? element : void 0;
10890
+ return {
10891
+ viewport,
10892
+ anchor,
10893
+ marker,
10894
+ selection: reviewSelection,
10895
+ comment,
10896
+ previewElement
9808
10897
  };
9809
10898
  });
10899
+ if (!draft) return;
9810
10900
  this.noteDraft = draft;
9811
10901
  this.render();
9812
10902
  }
@@ -9895,8 +10985,8 @@ var WebReviewKitApp = class {
9895
10985
  if (!environment) return;
9896
10986
  const scroll = item.scroll;
9897
10987
  if (scroll) {
9898
- runWithAutoScrollBehavior2(environment.document, () => {
9899
- setDocumentScrollInstantly2(environment, scroll);
10988
+ runWithAutoScrollBehavior(environment.document, () => {
10989
+ setDocumentScrollInstantly(environment, scroll);
9900
10990
  });
9901
10991
  await waitForNextFrame(environment);
9902
10992
  }
@@ -9914,6 +11004,8 @@ function createNoopController() {
9914
11004
  },
9915
11005
  setMode() {
9916
11006
  },
11007
+ async startElementReview() {
11008
+ },
9917
11009
  getMode() {
9918
11010
  return "idle";
9919
11011
  },
@@ -9932,39 +11024,6 @@ function createNoopController() {
9932
11024
  };
9933
11025
  }
9934
11026
 
9935
- // src/react-shell/target/target.ts
9936
- var HIDE_SCROLLBAR_STYLE_ID = "df-review-hide-scrollbar";
9937
- var setTargetScrollbarHidden = (targetDocument, hidden) => {
9938
- if (!targetDocument) return;
9939
- const existing = targetDocument.getElementById(HIDE_SCROLLBAR_STYLE_ID);
9940
- if (hidden) {
9941
- if (existing) return;
9942
- const style = targetDocument.createElement("style");
9943
- style.id = HIDE_SCROLLBAR_STYLE_ID;
9944
- style.textContent = "html{scrollbar-width:none}html::-webkit-scrollbar,body::-webkit-scrollbar{width:0;height:0;display:none}";
9945
- targetDocument.head?.appendChild(style);
9946
- } else {
9947
- existing?.remove();
9948
- }
9949
- };
9950
- var isEditableEventTarget2 = (event) => {
9951
- const path = event.composedPath?.() ?? [];
9952
- const element = path[0] ?? event.target;
9953
- if (!element || typeof element.tagName !== "string") return false;
9954
- const tag = element.tagName;
9955
- return tag === "INPUT" || tag === "TEXTAREA" || element.isContentEditable === true;
9956
- };
9957
- var getTargetOverlayState = (targetDocument) => ({
9958
- grid: Boolean(
9959
- targetDocument?.body.classList.contains("is-help") || targetDocument?.querySelector(".helper.onShow")
9960
- ),
9961
- figma: Boolean(
9962
- targetDocument?.querySelector(
9963
- ".helper-figma-root, .helper-figma-loading-backdrop"
9964
- )
9965
- )
9966
- });
9967
-
9968
11027
  // src/react-shell/hooks/review.frame.navigation.ts
9969
11028
  var bindReviewFrameNavigation = ({
9970
11029
  pageTargets,
@@ -10228,17 +11287,32 @@ var useReviewKitLifecycle = ({
10228
11287
 
10229
11288
  // src/react-shell/hooks/use.review.target.overlay.ts
10230
11289
  var import_react9 = require("react");
11290
+ var TARGET_OVERLAY_REFRESH_DELAYS = [80, 240, 600];
10231
11291
  var useReviewTargetOverlay = ({
10232
11292
  iframeRef,
10233
11293
  isFigmaOverlayAvailable,
10234
11294
  targetOverlayState,
10235
11295
  onTargetOverlayStateChange
10236
11296
  }) => {
10237
- const refreshTargetOverlayState = (0, import_react9.useCallback)(() => {
10238
- onTargetOverlayStateChange(
10239
- getTargetOverlayState(iframeRef.current?.contentDocument ?? void 0)
11297
+ const refreshTimersRef = (0, import_react9.useRef)([]);
11298
+ const clearRefreshTimers = (0, import_react9.useCallback)(() => {
11299
+ refreshTimersRef.current.forEach((timer) => window.clearTimeout(timer));
11300
+ refreshTimersRef.current = [];
11301
+ }, []);
11302
+ const updateTargetOverlayState = (0, import_react9.useCallback)(() => {
11303
+ const state = getTargetOverlayState(
11304
+ iframeRef.current?.contentDocument ?? void 0
10240
11305
  );
11306
+ onTargetOverlayStateChange(state);
11307
+ return state;
10241
11308
  }, [iframeRef, onTargetOverlayStateChange]);
11309
+ const refreshTargetOverlayState = (0, import_react9.useCallback)(() => {
11310
+ clearRefreshTimers();
11311
+ updateTargetOverlayState();
11312
+ refreshTimersRef.current = TARGET_OVERLAY_REFRESH_DELAYS.map(
11313
+ (delay) => window.setTimeout(updateTargetOverlayState, delay)
11314
+ );
11315
+ }, [clearRefreshTimers, updateTargetOverlayState]);
10242
11316
  const dispatchTargetOverlayHotkey = (0, import_react9.useCallback)(
10243
11317
  (overlay) => {
10244
11318
  const targetWindow = iframeRef.current?.contentWindow;
@@ -10274,19 +11348,17 @@ var useReviewTargetOverlay = ({
10274
11348
  );
10275
11349
  const closeTargetOverlay = (0, import_react9.useCallback)(
10276
11350
  (overlay) => {
10277
- const currentState = getTargetOverlayState(
10278
- iframeRef.current?.contentDocument ?? void 0
10279
- );
10280
- onTargetOverlayStateChange(currentState);
11351
+ const currentState = updateTargetOverlayState();
10281
11352
  if (!currentState[overlay]) return false;
10282
11353
  return dispatchTargetOverlayHotkey(overlay);
10283
11354
  },
10284
- [dispatchTargetOverlayHotkey, iframeRef, onTargetOverlayStateChange]
11355
+ [dispatchTargetOverlayHotkey, updateTargetOverlayState]
10285
11356
  );
10286
11357
  (0, import_react9.useEffect)(() => {
10287
11358
  if (isFigmaOverlayAvailable || !targetOverlayState.figma) return;
10288
11359
  closeTargetOverlay("figma");
10289
11360
  }, [closeTargetOverlay, isFigmaOverlayAvailable, targetOverlayState.figma]);
11361
+ (0, import_react9.useEffect)(() => clearRefreshTimers, [clearRefreshTimers]);
10290
11362
  return {
10291
11363
  closeTargetOverlay,
10292
11364
  refreshTargetOverlayState,
@@ -11337,8 +12409,18 @@ var useReviewShellData = ({
11337
12409
  [qaStatusFilter, scopeFilteredNumberedActiveItems]
11338
12410
  );
11339
12411
  const hiddenOverlayItemIdList = (0, import_react16.useMemo)(
11340
- () => Array.from(hiddenOverlayItemIds),
11341
- [hiddenOverlayItemIds]
12412
+ () => {
12413
+ const nextHiddenItemIds = new Set(hiddenOverlayItemIds);
12414
+ if (qaStatusFilter !== "all") {
12415
+ activeItems.forEach((item) => {
12416
+ if (normalizeReviewItemStatus(item.status) !== qaStatusFilter) {
12417
+ nextHiddenItemIds.add(item.id);
12418
+ }
12419
+ });
12420
+ }
12421
+ return Array.from(nextHiddenItemIds);
12422
+ },
12423
+ [activeItems, hiddenOverlayItemIds, qaStatusFilter]
11342
12424
  );
11343
12425
  const qaFilterCounts = (0, import_react16.useMemo)(() => {
11344
12426
  const counts = /* @__PURE__ */ new Map();
@@ -11544,7 +12626,7 @@ var useReviewShellHotkeys = ({
11544
12626
  if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) {
11545
12627
  return;
11546
12628
  }
11547
- if (isEditableEventTarget2(event)) return;
12629
+ if (isEditableEventTarget(event)) return;
11548
12630
  const actions = {
11549
12631
  r: () => {
11550
12632
  if (isRulerAvailable) onToggleRuler();
@@ -12072,6 +13154,112 @@ var getReviewModeWriteMode = (mode) => {
12072
13154
  var SOURCE_PANEL_MAX_WIDTH = 440;
12073
13155
  var SOURCE_PANEL_MIN_WIDTH = 240;
12074
13156
  var SOURCE_PANEL_MAX_HEIGHT = 260;
13157
+ var SOURCE_TREE_PANEL_CLOSE_DELAY_MS = 180;
13158
+ var waitForFrame = (targetWindow) => new Promise((resolve) => {
13159
+ (targetWindow ?? window).requestAnimationFrame(() => resolve());
13160
+ });
13161
+ var waitForMs = (ms) => new Promise((resolve) => {
13162
+ window.setTimeout(resolve, ms);
13163
+ });
13164
+ var getScrollElement = (targetDocument) => targetDocument.scrollingElement;
13165
+ var scrollElementInTarget = (element, block) => {
13166
+ const targetWindow = element.ownerDocument.defaultView;
13167
+ if (!targetWindow) return;
13168
+ const targetDocument = element.ownerDocument;
13169
+ const scrollElement = getScrollElement(targetDocument);
13170
+ const rect = element.getBoundingClientRect();
13171
+ const currentLeft = scrollElement?.scrollLeft ?? targetWindow.scrollX;
13172
+ const currentTop = scrollElement?.scrollTop ?? targetWindow.scrollY;
13173
+ const clientWidth = scrollElement?.clientWidth ?? targetWindow.innerWidth;
13174
+ const clientHeight = scrollElement?.clientHeight ?? targetWindow.innerHeight;
13175
+ const scrollWidth = scrollElement?.scrollWidth ?? targetDocument.documentElement.scrollWidth;
13176
+ const scrollHeight = scrollElement?.scrollHeight ?? targetDocument.documentElement.scrollHeight;
13177
+ const nextLeft = clamp(
13178
+ currentLeft + rect.left + rect.width / 2 - clientWidth / 2,
13179
+ 0,
13180
+ Math.max(0, scrollWidth - clientWidth)
13181
+ );
13182
+ const nextTop = block === "center" ? clamp(
13183
+ currentTop + rect.top + rect.height / 2 - clientHeight / 2,
13184
+ 0,
13185
+ Math.max(0, scrollHeight - clientHeight)
13186
+ ) : clamp(
13187
+ currentTop + rect.top,
13188
+ 0,
13189
+ Math.max(0, scrollHeight - clientHeight)
13190
+ );
13191
+ runWithAutoScrollBehavior(targetDocument, () => {
13192
+ if (scrollElement) {
13193
+ scrollElement.scrollLeft = Math.round(nextLeft);
13194
+ scrollElement.scrollTop = Math.round(nextTop);
13195
+ return;
13196
+ }
13197
+ targetWindow.scrollTo(Math.round(nextLeft), Math.round(nextTop));
13198
+ });
13199
+ };
13200
+ var centerFrameScrollOnElement = (frameScroll, frame, element) => {
13201
+ if (!frameScroll || !frame) return;
13202
+ const frameScrollRect = frameScroll.getBoundingClientRect();
13203
+ const frameRect = frame.getBoundingClientRect();
13204
+ const elementRect = element.getBoundingClientRect();
13205
+ const elementHostCenterX = frameRect.left + elementRect.left + elementRect.width / 2;
13206
+ const elementHostCenterY = frameRect.top + elementRect.top + elementRect.height / 2;
13207
+ const visibleCenterX = frameScrollRect.left + frameScrollRect.width / 2;
13208
+ const visibleCenterY = frameScrollRect.top + frameScrollRect.height / 2;
13209
+ const nextLeft = clamp(
13210
+ frameScroll.scrollLeft + elementHostCenterX - visibleCenterX,
13211
+ 0,
13212
+ Math.max(0, frameScroll.scrollWidth - frameScroll.clientWidth)
13213
+ );
13214
+ const nextTop = clamp(
13215
+ frameScroll.scrollTop + elementHostCenterY - visibleCenterY,
13216
+ 0,
13217
+ Math.max(0, frameScroll.scrollHeight - frameScroll.clientHeight)
13218
+ );
13219
+ const previousScrollBehavior = frameScroll.style.scrollBehavior;
13220
+ frameScroll.style.scrollBehavior = "auto";
13221
+ frameScroll.scrollLeft = Math.round(nextLeft);
13222
+ frameScroll.scrollTop = Math.round(nextTop);
13223
+ frameScroll.style.scrollBehavior = previousScrollBehavior;
13224
+ };
13225
+ var getSectionOutlineFilterTerms = (value) => value.trim().toLowerCase().split(/\s+/).filter(Boolean);
13226
+ var getSectionOutlineEntryCount = (entries) => entries.reduce(
13227
+ (count, entry) => count + 1 + getSectionOutlineEntryCount(entry.children),
13228
+ 0
13229
+ );
13230
+ var DEFAULT_COLLAPSED_ROOT_LABELS = /* @__PURE__ */ new Set(["FrameHeader", "FrameFooter"]);
13231
+ var getDefaultCollapsedSectionOutlineIds = (entries) => {
13232
+ const collapsedIds = /* @__PURE__ */ new Set();
13233
+ const visit = (entry) => {
13234
+ const shouldCollapseRoot = entry.depth === 1 && DEFAULT_COLLAPSED_ROOT_LABELS.has(entry.label);
13235
+ if ((entry.depth >= 2 || shouldCollapseRoot) && entry.children.length > 0) {
13236
+ collapsedIds.add(entry.id);
13237
+ }
13238
+ entry.children.forEach(visit);
13239
+ };
13240
+ entries.forEach(visit);
13241
+ return collapsedIds;
13242
+ };
13243
+ var matchesSectionOutlineFilter = (entry, terms) => {
13244
+ if (terms.length === 0) return true;
13245
+ const text = [
13246
+ entry.label,
13247
+ entry.filePath,
13248
+ entry.source?.file,
13249
+ entry.data?.file
13250
+ ].filter(Boolean).join(" ").toLowerCase();
13251
+ return terms.every((term) => text.includes(term));
13252
+ };
13253
+ var filterSectionOutlineEntries = (entries, terms) => {
13254
+ if (terms.length === 0) return entries;
13255
+ return entries.flatMap((entry) => {
13256
+ const children = filterSectionOutlineEntries(entry.children, terms);
13257
+ if (!matchesSectionOutlineFilter(entry, terms) && children.length === 0) {
13258
+ return [];
13259
+ }
13260
+ return [{ ...entry, children }];
13261
+ });
13262
+ };
12075
13263
  var ReviewShell = ({
12076
13264
  projectId,
12077
13265
  pages,
@@ -12143,7 +13331,11 @@ var ReviewShell = ({
12143
13331
  });
12144
13332
  const sourceShortcutCleanupRef = (0, import_react19.useRef)(null);
12145
13333
  const sourceInspectorInteractionRef = (0, import_react19.useRef)(false);
13334
+ const [sidePanel, setSidePanel] = (0, import_react19.useState)("qa");
12146
13335
  const [sourceInspectorState, setSourceInspectorState] = (0, import_react19.useState)(null);
13336
+ const [sectionOutline, setSectionOutline] = (0, import_react19.useState)(null);
13337
+ const [sectionOutlineFilter, setSectionOutlineFilter] = (0, import_react19.useState)("");
13338
+ const [collapsedSectionOutlineIds, setCollapsedSectionOutlineIds] = (0, import_react19.useState)(() => /* @__PURE__ */ new Set());
12147
13339
  const [isAllQaVisible, setIsAllQaVisible] = (0, import_react19.useState)(false);
12148
13340
  const sourceOpenOptions = (0, import_react19.useMemo)(
12149
13341
  () => ({
@@ -12152,7 +13344,42 @@ var ReviewShell = ({
12152
13344
  }),
12153
13345
  [sourceInspector, sourceRoot]
12154
13346
  );
13347
+ const sourceCandidateOptions = (0, import_react19.useMemo)(
13348
+ () => ({
13349
+ ignore: sourceInspector?.ignore,
13350
+ includePlacer: sourceInspector?.includePlacer
13351
+ }),
13352
+ [sourceInspector]
13353
+ );
13354
+ const sectionOutlineOptions = (0, import_react19.useMemo)(
13355
+ () => ({
13356
+ includePlacer: sourceInspector?.includePlacer,
13357
+ ignore: sourceInspector?.ignore,
13358
+ maxDepth: sourceInspector?.maxDepth
13359
+ }),
13360
+ [sourceInspector]
13361
+ );
12155
13362
  const isSourceInspectorEnabled = sourceInspector?.enabled !== false;
13363
+ const isSourceTreeHoverOutlineEnabled = sourceInspector?.hoverOutline !== false;
13364
+ const isQaPanelVisible = isListVisible && sidePanel === "qa";
13365
+ const isSourceTreePanelVisible = isSourceInspectorEnabled && isListVisible && sidePanel === "source";
13366
+ const sectionOutlineFilterTerms = (0, import_react19.useMemo)(
13367
+ () => getSectionOutlineFilterTerms(sectionOutlineFilter),
13368
+ [sectionOutlineFilter]
13369
+ );
13370
+ const filteredSectionOutline = (0, import_react19.useMemo)(
13371
+ () => sectionOutline ? filterSectionOutlineEntries(sectionOutline, sectionOutlineFilterTerms) : [],
13372
+ [sectionOutline, sectionOutlineFilterTerms]
13373
+ );
13374
+ const sectionOutlineTotalCount = (0, import_react19.useMemo)(
13375
+ () => getSectionOutlineEntryCount(sectionOutline ?? []),
13376
+ [sectionOutline]
13377
+ );
13378
+ const filteredSectionOutlineCount = (0, import_react19.useMemo)(
13379
+ () => getSectionOutlineEntryCount(filteredSectionOutline),
13380
+ [filteredSectionOutline]
13381
+ );
13382
+ const isSectionOutlineFiltering = sectionOutlineFilterTerms.length > 0;
12156
13383
  const {
12157
13384
  activeItems,
12158
13385
  activeRemainingItemCount,
@@ -12304,7 +13531,6 @@ var ReviewShell = ({
12304
13531
  });
12305
13532
  const {
12306
13533
  clearSelectedItem,
12307
- closeTargetOverlay,
12308
13534
  initReviewKit,
12309
13535
  reloadReviewKit,
12310
13536
  restoreReviewItem,
@@ -12442,9 +13668,6 @@ var ReviewShell = ({
12442
13668
  const writeMode = getReviewModeWriteMode(nextMode);
12443
13669
  if (writeMode && !activeAdapterEntry.writeModes.includes(writeMode)) return;
12444
13670
  closeRuler();
12445
- if (nextMode === "element") {
12446
- closeTargetOverlay("figma");
12447
- }
12448
13671
  setControllerReviewMode(nextMode);
12449
13672
  };
12450
13673
  useReviewShellHotkeys({
@@ -12481,10 +13704,23 @@ var ReviewShell = ({
12481
13704
  );
12482
13705
  setTargetFigmaState(config ? { targetSrc, config } : null);
12483
13706
  }, [iframeRef, targetSrc]);
13707
+ (0, import_react19.useEffect)(() => {
13708
+ const targetDocument = iframeRef.current?.contentDocument;
13709
+ setTargetFigmaOverlayLocked(targetDocument, mode === "element");
13710
+ return () => {
13711
+ setTargetFigmaOverlayLocked(targetDocument, false);
13712
+ };
13713
+ }, [iframeRef, mode, targetSrc]);
12484
13714
  const clearSourceInspector = (0, import_react19.useCallback)(() => {
12485
13715
  sourceInspectorInteractionRef.current = false;
12486
13716
  setSourceInspectorState(null);
12487
13717
  }, []);
13718
+ (0, import_react19.useEffect)(() => {
13719
+ clearSourceInspector();
13720
+ setSectionOutlineFilter("");
13721
+ setCollapsedSectionOutlineIds(/* @__PURE__ */ new Set());
13722
+ setSectionOutline(null);
13723
+ }, [clearSourceInspector, targetSrc]);
12488
13724
  const getSourceInspectorRect = (0, import_react19.useCallback)(
12489
13725
  (element) => {
12490
13726
  const frame = iframeRef.current;
@@ -12538,13 +13774,15 @@ var ReviewShell = ({
12538
13774
  );
12539
13775
  const showSourceInspectorForTarget = (0, import_react19.useCallback)(
12540
13776
  (target2, isPinned = false) => {
12541
- const candidates = getSourceCandidates(target2).map((candidate) => ({
12542
- ...candidate,
12543
- openUrl: getSourceOpenUrl(candidate.source, {
12544
- ...sourceOpenOptions,
12545
- omitPosition: !candidate.usesPosition
13777
+ const candidates = getSourceCandidates(target2, sourceCandidateOptions).map(
13778
+ (candidate) => ({
13779
+ ...candidate,
13780
+ openUrl: getSourceOpenUrl(candidate.source, {
13781
+ ...sourceOpenOptions,
13782
+ omitPosition: !candidate.usesPosition
13783
+ })
12546
13784
  })
12547
- }));
13785
+ );
12548
13786
  const firstCandidate = candidates[0];
12549
13787
  const rect = firstCandidate ? getSourceInspectorRect(firstCandidate.element) : null;
12550
13788
  if (!firstCandidate || !rect) {
@@ -12566,12 +13804,16 @@ var ReviewShell = ({
12566
13804
  [
12567
13805
  getSourceInspectorPanelPosition,
12568
13806
  getSourceInspectorRect,
13807
+ sourceCandidateOptions,
12569
13808
  sourceOpenOptions
12570
13809
  ]
12571
13810
  );
12572
13811
  const showSourceOutlineForTarget = (0, import_react19.useCallback)(
12573
13812
  (target2) => {
12574
- const firstCandidate = getSourceCandidates(target2)[0];
13813
+ const firstCandidate = getSourceCandidates(
13814
+ target2,
13815
+ sourceCandidateOptions
13816
+ )[0];
12575
13817
  const rect = firstCandidate ? getSourceInspectorRect(firstCandidate.element) : null;
12576
13818
  if (!firstCandidate || !rect) {
12577
13819
  setSourceInspectorState(null);
@@ -12588,8 +13830,35 @@ var ReviewShell = ({
12588
13830
  });
12589
13831
  return firstCandidate;
12590
13832
  },
12591
- [getSourceInspectorRect]
13833
+ [getSourceInspectorRect, sourceCandidateOptions]
13834
+ );
13835
+ const showSourceOutlineForElement = (0, import_react19.useCallback)(
13836
+ (element) => {
13837
+ if (!isSourceTreeHoverOutlineEnabled) return;
13838
+ const rect = getSourceInspectorRect(element);
13839
+ if (!rect) {
13840
+ setSourceInspectorState(
13841
+ (current) => current?.isPinned ? current : null
13842
+ );
13843
+ return;
13844
+ }
13845
+ setSourceInspectorState(
13846
+ (current) => current?.isPinned ? current : {
13847
+ candidates: [],
13848
+ isPinned: false,
13849
+ panelLeft: 0,
13850
+ panelMaxWidth: SOURCE_PANEL_MAX_WIDTH,
13851
+ panelRight: null,
13852
+ panelTop: 0,
13853
+ rect
13854
+ }
13855
+ );
13856
+ },
13857
+ [getSourceInspectorRect, isSourceTreeHoverOutlineEnabled]
12592
13858
  );
13859
+ const clearSourceOutlineHover = (0, import_react19.useCallback)(() => {
13860
+ setSourceInspectorState((current) => current?.isPinned ? current : null);
13861
+ }, []);
12593
13862
  const openSourceCandidate = (0, import_react19.useCallback)(
12594
13863
  (candidate) => {
12595
13864
  const didOpen = openSourceInEditor(candidate.source, {
@@ -12601,6 +13870,153 @@ var ReviewShell = ({
12601
13870
  },
12602
13871
  [clearSourceInspector, showToast, sourceOpenOptions]
12603
13872
  );
13873
+ const getCurrentSectionOutline = (0, import_react19.useCallback)(
13874
+ () => {
13875
+ let frameDocument = null;
13876
+ try {
13877
+ frameDocument = iframeRef.current?.contentDocument ?? null;
13878
+ } catch {
13879
+ frameDocument = null;
13880
+ }
13881
+ return frameDocument ? getSectionOutline(frameDocument, sectionOutlineOptions) : [];
13882
+ },
13883
+ [iframeRef, sectionOutlineOptions]
13884
+ );
13885
+ const setSectionOutlineWithDefaultCollapse = (0, import_react19.useCallback)(
13886
+ (nextSectionOutline) => {
13887
+ setSectionOutline(nextSectionOutline);
13888
+ setCollapsedSectionOutlineIds(
13889
+ getDefaultCollapsedSectionOutlineIds(nextSectionOutline)
13890
+ );
13891
+ },
13892
+ []
13893
+ );
13894
+ (0, import_react19.useEffect)(() => {
13895
+ if (sidePanel !== "source" || !isListVisible) return void 0;
13896
+ const refreshSectionOutline = () => {
13897
+ setSectionOutlineWithDefaultCollapse(getCurrentSectionOutline());
13898
+ };
13899
+ const animationFrame = window.requestAnimationFrame(refreshSectionOutline);
13900
+ const firstTimeout = window.setTimeout(refreshSectionOutline, 120);
13901
+ const secondTimeout = window.setTimeout(refreshSectionOutline, 500);
13902
+ return () => {
13903
+ window.cancelAnimationFrame(animationFrame);
13904
+ window.clearTimeout(firstTimeout);
13905
+ window.clearTimeout(secondTimeout);
13906
+ };
13907
+ }, [
13908
+ getCurrentSectionOutline,
13909
+ isListVisible,
13910
+ setSectionOutlineWithDefaultCollapse,
13911
+ sidePanel,
13912
+ targetSrc
13913
+ ]);
13914
+ const toggleQaPanel = (0, import_react19.useCallback)(() => {
13915
+ setSidePanel("qa");
13916
+ setIsListVisible((current) => sidePanel === "qa" ? !current : true);
13917
+ }, [setIsListVisible, sidePanel]);
13918
+ const toggleSourceTreePanel = (0, import_react19.useCallback)(() => {
13919
+ if (!isSourceInspectorEnabled) return;
13920
+ if (sidePanel === "source" && isListVisible) {
13921
+ setIsListVisible(false);
13922
+ return;
13923
+ }
13924
+ setSidePanel("source");
13925
+ setSectionOutlineWithDefaultCollapse(getCurrentSectionOutline());
13926
+ setIsListVisible(true);
13927
+ }, [
13928
+ getCurrentSectionOutline,
13929
+ isListVisible,
13930
+ isSourceInspectorEnabled,
13931
+ setSectionOutlineWithDefaultCollapse,
13932
+ setIsListVisible,
13933
+ sidePanel
13934
+ ]);
13935
+ const toggleSectionOutlineEntry = (0, import_react19.useCallback)((entryId) => {
13936
+ setCollapsedSectionOutlineIds((current) => {
13937
+ const next = new Set(current);
13938
+ if (next.has(entryId)) {
13939
+ next.delete(entryId);
13940
+ } else {
13941
+ next.add(entryId);
13942
+ }
13943
+ return next;
13944
+ });
13945
+ }, []);
13946
+ const scrollToSection = (0, import_react19.useCallback)((entry) => {
13947
+ scrollElementInTarget(entry.element, "start");
13948
+ centerFrameScrollOnElement(
13949
+ frameScrollRef.current,
13950
+ iframeRef.current,
13951
+ entry.element
13952
+ );
13953
+ }, [frameScrollRef, iframeRef]);
13954
+ const openSectionSource = (0, import_react19.useCallback)(
13955
+ (entry) => {
13956
+ const didOpen = openSourceInEditor(entry.source, {
13957
+ ...sourceOpenOptions,
13958
+ omitPosition: true
13959
+ });
13960
+ showToast(didOpen ? "Source opened" : "Source root required");
13961
+ },
13962
+ [showToast, sourceOpenOptions]
13963
+ );
13964
+ const openSectionData = (0, import_react19.useCallback)(
13965
+ (entry) => {
13966
+ const didOpen = openSourceInEditor(entry.data, sourceOpenOptions);
13967
+ showToast(didOpen ? "Data opened" : "Data hint not found");
13968
+ },
13969
+ [showToast, sourceOpenOptions]
13970
+ );
13971
+ const startSectionDomReview = (0, import_react19.useCallback)(
13972
+ (entry) => {
13973
+ if (!canWriteDom) {
13974
+ showToast("DOM QA unavailable");
13975
+ return;
13976
+ }
13977
+ clearSourceInspector();
13978
+ setIsListVisible(false);
13979
+ let targetWindow = null;
13980
+ try {
13981
+ targetWindow = entry.element.ownerDocument.defaultView ?? iframeRef.current?.contentWindow ?? null;
13982
+ } catch {
13983
+ targetWindow = null;
13984
+ }
13985
+ void waitForMs(SOURCE_TREE_PANEL_CLOSE_DELAY_MS).then(async () => {
13986
+ initReviewKit();
13987
+ await waitForFrame(targetWindow);
13988
+ const controller = controllerRef.current;
13989
+ if (!controller) {
13990
+ showToast("DOM QA unavailable");
13991
+ return;
13992
+ }
13993
+ scrollElementInTarget(entry.element, "center");
13994
+ await waitForFrame(targetWindow);
13995
+ centerFrameScrollOnElement(
13996
+ frameScrollRef.current,
13997
+ iframeRef.current,
13998
+ entry.element
13999
+ );
14000
+ await waitForFrame(targetWindow);
14001
+ await controller.startElementReview(entry.element);
14002
+ await waitForFrame(targetWindow);
14003
+ setMode(controller.getMode());
14004
+ }).catch(() => {
14005
+ setMode(controllerRef.current?.getMode() ?? "idle");
14006
+ });
14007
+ },
14008
+ [
14009
+ canWriteDom,
14010
+ clearSourceInspector,
14011
+ controllerRef,
14012
+ frameScrollRef,
14013
+ iframeRef,
14014
+ initReviewKit,
14015
+ setIsListVisible,
14016
+ setMode,
14017
+ showToast
14018
+ ]
14019
+ );
12604
14020
  const cleanupSourceOpenShortcut = (0, import_react19.useCallback)(() => {
12605
14021
  sourceShortcutCleanupRef.current?.();
12606
14022
  sourceShortcutCleanupRef.current = null;
@@ -12627,6 +14043,13 @@ var ReviewShell = ({
12627
14043
  cursor: crosshair !important;
12628
14044
  }
12629
14045
 
14046
+ html[${optionAttribute}="true"] .helper-figma-root,
14047
+ html[${optionAttribute}="true"] .helper-figma-root *,
14048
+ html[${optionAttribute}="true"] .helper-figma-loading-backdrop,
14049
+ html[${optionAttribute}="true"] .helper-figma-loading-backdrop * {
14050
+ pointer-events: none !important;
14051
+ }
14052
+
12630
14053
  html[${optionAttribute}="true"] body::before {
12631
14054
  position: fixed !important;
12632
14055
  z-index: 2147483647 !important;
@@ -12641,7 +14064,7 @@ var ReviewShell = ({
12641
14064
  background: rgba(15, 23, 42, 0.86) !important;
12642
14065
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.24) !important;
12643
14066
  content: "Source select" !important;
12644
- font: 700 12px/1 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
14067
+ font: 500 12px/1 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
12645
14068
  pointer-events: none !important;
12646
14069
  }
12647
14070
 
@@ -12658,7 +14081,7 @@ var ReviewShell = ({
12658
14081
  color: #ffffff !important;
12659
14082
  background: rgba(15, 23, 42, 0.9) !important;
12660
14083
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.28) !important;
12661
- font: 800 11px/1.35 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace !important;
14084
+ font: 500 11px/1.35 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace !important;
12662
14085
  overflow-wrap: anywhere !important;
12663
14086
  pointer-events: none !important;
12664
14087
  white-space: normal !important;
@@ -12759,8 +14182,9 @@ var ReviewShell = ({
12759
14182
  }
12760
14183
  };
12761
14184
  const handleMouseMove = (event) => {
14185
+ if (isSourcePanelPinned) return;
12762
14186
  lastSourceTarget = event.target;
12763
- const candidates = getSourceCandidates(event.target);
14187
+ const candidates = getSourceCandidates(event.target, sourceCandidateOptions);
12764
14188
  const sourceElement = candidates[0]?.element ?? null;
12765
14189
  if (event.altKey && !isSourceSelecting) {
12766
14190
  setSourceSelecting(true);
@@ -12794,6 +14218,7 @@ var ReviewShell = ({
12794
14218
  return;
12795
14219
  }
12796
14220
  if (!isOptionKeyEvent(event)) return;
14221
+ if (isSourcePanelPinned) return;
12797
14222
  cancelReviewMode();
12798
14223
  setSourceSelecting(true);
12799
14224
  };
@@ -12844,6 +14269,7 @@ var ReviewShell = ({
12844
14269
  iframeRef,
12845
14270
  isSourceInspectorEnabled,
12846
14271
  showToast,
14272
+ sourceCandidateOptions,
12847
14273
  showSourceOutlineForTarget,
12848
14274
  showSourceInspectorForTarget
12849
14275
  ]);
@@ -12853,8 +14279,25 @@ var ReviewShell = ({
12853
14279
  const loadTargetFrame = (0, import_react19.useCallback)(() => {
12854
14280
  initReviewKit();
12855
14281
  refreshTargetFigmaConfig();
14282
+ setTargetFigmaOverlayLocked(
14283
+ iframeRef.current?.contentDocument,
14284
+ mode === "element"
14285
+ );
12856
14286
  bindSourceOpenShortcut();
12857
- }, [bindSourceOpenShortcut, initReviewKit, refreshTargetFigmaConfig]);
14287
+ if (sidePanel === "source" && isListVisible) {
14288
+ setSectionOutlineWithDefaultCollapse(getCurrentSectionOutline());
14289
+ }
14290
+ }, [
14291
+ bindSourceOpenShortcut,
14292
+ getCurrentSectionOutline,
14293
+ iframeRef,
14294
+ initReviewKit,
14295
+ isListVisible,
14296
+ mode,
14297
+ refreshTargetFigmaConfig,
14298
+ setSectionOutlineWithDefaultCollapse,
14299
+ sidePanel
14300
+ ]);
12858
14301
  (0, import_react19.useEffect)(() => {
12859
14302
  const frame = window.requestAnimationFrame(bindSourceOpenShortcut);
12860
14303
  return () => window.cancelAnimationFrame(frame);
@@ -12934,6 +14377,112 @@ var ReviewShell = ({
12934
14377
  onRefreshReviewData: refreshReviewData2,
12935
14378
  onToast: showToast
12936
14379
  });
14380
+ const renderSectionOutlineEntry = (entry) => {
14381
+ const hasChildren = entry.children.length > 0;
14382
+ const isCollapsed = !isSectionOutlineFiltering && collapsedSectionOutlineIds.has(entry.id);
14383
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
14384
+ "div",
14385
+ {
14386
+ className: `df-review-section-outline-item is-depth-${entry.depth}`,
14387
+ children: [
14388
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
14389
+ "div",
14390
+ {
14391
+ className: "df-review-section-outline-row",
14392
+ style: { paddingLeft: `${Math.max(0, entry.depth - 1) * 12 + 6}px` },
14393
+ onMouseEnter: () => showSourceOutlineForElement(entry.element),
14394
+ onMouseLeave: clearSourceOutlineHover,
14395
+ onMouseOver: () => showSourceOutlineForElement(entry.element),
14396
+ onMouseOut: (event) => {
14397
+ if (event.relatedTarget instanceof Node && event.currentTarget.contains(event.relatedTarget)) {
14398
+ return;
14399
+ }
14400
+ clearSourceOutlineHover();
14401
+ },
14402
+ onPointerEnter: () => showSourceOutlineForElement(entry.element),
14403
+ onPointerLeave: clearSourceOutlineHover,
14404
+ children: [
14405
+ hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14406
+ "button",
14407
+ {
14408
+ "aria-label": isCollapsed ? `Expand ${entry.label}` : `Collapse ${entry.label}`,
14409
+ "aria-expanded": !isCollapsed,
14410
+ className: `df-review-section-outline-toggle${isCollapsed ? " is-collapsed" : ""}`,
14411
+ type: "button",
14412
+ onClick: () => toggleSectionOutlineEntry(entry.id),
14413
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ChevronDown, { "aria-hidden": "true" })
14414
+ }
14415
+ ) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14416
+ "span",
14417
+ {
14418
+ "aria-hidden": "true",
14419
+ className: "df-review-section-outline-toggle is-placeholder"
14420
+ }
14421
+ ),
14422
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14423
+ "button",
14424
+ {
14425
+ className: "df-review-section-outline-name",
14426
+ type: "button",
14427
+ onClick: () => scrollToSection(entry),
14428
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: entry.label })
14429
+ }
14430
+ ),
14431
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "df-review-section-outline-links", children: [
14432
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14433
+ "button",
14434
+ {
14435
+ "aria-label": `Open ${entry.label} data`,
14436
+ className: "df-review-section-outline-link",
14437
+ title: "Open data",
14438
+ type: "button",
14439
+ disabled: !entry.data?.file,
14440
+ onClick: () => openSectionData(entry),
14441
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Database, { "aria-hidden": "true" })
14442
+ }
14443
+ ),
14444
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14445
+ "button",
14446
+ {
14447
+ "aria-label": `Open ${entry.label} source`,
14448
+ className: "df-review-section-outline-link",
14449
+ title: "Open source",
14450
+ type: "button",
14451
+ disabled: !entry.source?.file,
14452
+ onClick: () => openSectionSource(entry),
14453
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CodeXml, { "aria-hidden": "true" })
14454
+ }
14455
+ ),
14456
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14457
+ "span",
14458
+ {
14459
+ "aria-hidden": "true",
14460
+ className: "df-review-section-outline-divider",
14461
+ children: "|"
14462
+ }
14463
+ ),
14464
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14465
+ "button",
14466
+ {
14467
+ "aria-label": `Start DOM QA for ${entry.label}`,
14468
+ className: "df-review-section-outline-link is-dom-select",
14469
+ title: "DOM select",
14470
+ type: "button",
14471
+ disabled: !canWriteDom,
14472
+ onClick: () => startSectionDomReview(entry),
14473
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SquareMousePointer, { "aria-hidden": "true" })
14474
+ }
14475
+ )
14476
+ ] })
14477
+ ]
14478
+ }
14479
+ ),
14480
+ hasChildren && !isCollapsed && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "df-review-section-outline-children", children: entry.children.map(renderSectionOutlineEntry) })
14481
+ ]
14482
+ },
14483
+ entry.id
14484
+ );
14485
+ };
12937
14486
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
12938
14487
  "div",
12939
14488
  {
@@ -12964,13 +14513,6 @@ var ReviewShell = ({
12964
14513
  onOpenSettings: openFigmaSettings
12965
14514
  }
12966
14515
  ),
12967
- currentPagePresenceUsers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "df-review-presence-row", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
12968
- PresenceOverlay,
12969
- {
12970
- presenceSessionId,
12971
- users: currentPagePresenceUsers
12972
- }
12973
- ) }),
12974
14516
  isSitemapOpen && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
12975
14517
  SitemapModal,
12976
14518
  {
@@ -13023,19 +14565,40 @@ var ReviewShell = ({
13023
14565
  }
13024
14566
  ),
13025
14567
  toastMessage && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "df-review-copy-toast", role: "status", children: toastMessage }),
13026
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "df-review-side-rail", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
13027
- "button",
13028
- {
13029
- "aria-label": isListVisible ? "Hide QA list" : "Show QA list",
13030
- className: "df-review-side-toggle",
13031
- type: "button",
13032
- onClick: () => setIsListVisible((current) => !current),
13033
- children: [
13034
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(GripVertical, {}) }),
13035
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("strong", { children: "QA" })
13036
- ]
13037
- }
13038
- ) }),
14568
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "df-review-side-rail", children: [
14569
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14570
+ "button",
14571
+ {
14572
+ "aria-label": isQaPanelVisible ? "Hide QA list" : "Show QA list",
14573
+ "aria-pressed": isQaPanelVisible,
14574
+ className: `df-review-side-toggle${isQaPanelVisible ? " is-active" : ""}`,
14575
+ type: "button",
14576
+ onClick: toggleQaPanel,
14577
+ title: "QA",
14578
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(FileText, {}) })
14579
+ }
14580
+ ),
14581
+ isSourceInspectorEnabled && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14582
+ "button",
14583
+ {
14584
+ "aria-controls": "df-review-section-outline",
14585
+ "aria-label": isSourceTreePanelVisible ? "Hide source tree" : "Show source tree",
14586
+ "aria-pressed": isSourceTreePanelVisible,
14587
+ className: `df-review-side-toggle${isSourceTreePanelVisible ? " is-active" : ""}`,
14588
+ type: "button",
14589
+ onClick: toggleSourceTreePanel,
14590
+ title: "Source Tree",
14591
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Search, {}) })
14592
+ }
14593
+ ),
14594
+ currentPagePresenceUsers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14595
+ PresenceOverlay,
14596
+ {
14597
+ presenceSessionId,
14598
+ users: currentPagePresenceUsers
14599
+ }
14600
+ )
14601
+ ] }),
13039
14602
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
13040
14603
  ReviewQaPanel,
13041
14604
  {
@@ -13046,7 +14609,7 @@ var ReviewShell = ({
13046
14609
  filteredNumberedActiveItems,
13047
14610
  getItemPresetScope,
13048
14611
  hiddenOverlayItemIds,
13049
- isListVisible,
14612
+ isListVisible: isQaPanelVisible,
13050
14613
  isAllQaVisible,
13051
14614
  isRemoteSource,
13052
14615
  copiedPromptKey,
@@ -13074,6 +14637,49 @@ var ReviewShell = ({
13074
14637
  onToggleItemOverlayVisibility: toggleItemOverlayVisibility
13075
14638
  }
13076
14639
  ),
14640
+ isSourceInspectorEnabled && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14641
+ "aside",
14642
+ {
14643
+ className: "df-review-source-tree-panel",
14644
+ "aria-hidden": !isSourceTreePanelVisible,
14645
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { id: "df-review-section-outline", className: "df-review-section-outline", children: [
14646
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "df-review-section-outline-head", children: [
14647
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { children: [
14648
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("strong", { children: "Source Tree" }),
14649
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("small", { children: isSectionOutlineFiltering ? `${filteredSectionOutlineCount} / ${sectionOutlineTotalCount} results` : `${sectionOutline?.length ?? 0} roots` })
14650
+ ] }),
14651
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "df-review-section-outline-filter", children: [
14652
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Search, { "aria-hidden": "true" }),
14653
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14654
+ "input",
14655
+ {
14656
+ "aria-label": "Filter source tree",
14657
+ type: "text",
14658
+ value: sectionOutlineFilter,
14659
+ placeholder: "Filter",
14660
+ autoComplete: "off",
14661
+ enterKeyHint: "search",
14662
+ spellCheck: false,
14663
+ onChange: (event) => setSectionOutlineFilter(event.currentTarget.value)
14664
+ }
14665
+ ),
14666
+ sectionOutlineFilter && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
14667
+ "button",
14668
+ {
14669
+ "aria-label": "Clear source tree filter",
14670
+ className: "df-review-section-outline-filter-clear",
14671
+ type: "button",
14672
+ onMouseDown: (event) => event.preventDefault(),
14673
+ onClick: () => setSectionOutlineFilter(""),
14674
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(X, { "aria-hidden": "true" })
14675
+ }
14676
+ )
14677
+ ] })
14678
+ ] }),
14679
+ filteredSectionOutline.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "df-review-section-outline-list", children: filteredSectionOutline.map(renderSectionOutlineEntry) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "df-review-section-outline-empty", children: isSectionOutlineFiltering ? "No source matches" : "No sections found" })
14680
+ ] })
14681
+ }
14682
+ ),
13077
14683
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
13078
14684
  ReviewTargetFrame,
13079
14685
  {
@@ -13145,7 +14751,7 @@ var ReviewShell = ({
13145
14751
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "df-review-source-candidate-list", children: sourceInspectorState.candidates.map((candidate) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
13146
14752
  "button",
13147
14753
  {
13148
- className: "df-review-source-candidate",
14754
+ className: `df-review-source-candidate is-${candidate.kind}`,
13149
14755
  type: "button",
13150
14756
  onClick: (event) => {
13151
14757
  event.preventDefault();
@@ -13155,7 +14761,7 @@ var ReviewShell = ({
13155
14761
  children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "df-review-source-candidate-main", children: [
13156
14762
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("strong", { children: candidate.label }),
13157
14763
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: candidate.filePath }),
13158
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("small", { children: candidate.positionLabel || (candidate.usesPosition ? "" : "file only") })
14764
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("small", { children: candidate.positionLabel || "-:-" })
13159
14765
  ] })
13160
14766
  },
13161
14767
  candidate.id
@@ -13379,12 +14985,16 @@ lucide-react/dist/esm/context.mjs:
13379
14985
  lucide-react/dist/esm/Icon.mjs:
13380
14986
  lucide-react/dist/esm/createLucideIcon.mjs:
13381
14987
  lucide-react/dist/esm/icons/bot.mjs:
14988
+ lucide-react/dist/esm/icons/chevron-down.mjs:
13382
14989
  lucide-react/dist/esm/icons/circle-question-mark.mjs:
14990
+ lucide-react/dist/esm/icons/code-xml.mjs:
13383
14991
  lucide-react/dist/esm/icons/copy.mjs:
14992
+ lucide-react/dist/esm/icons/database.mjs:
14993
+ lucide-react/dist/esm/icons/ellipsis.mjs:
13384
14994
  lucide-react/dist/esm/icons/external-link.mjs:
13385
14995
  lucide-react/dist/esm/icons/eye-off.mjs:
13386
14996
  lucide-react/dist/esm/icons/eye.mjs:
13387
- lucide-react/dist/esm/icons/grip-vertical.mjs:
14997
+ lucide-react/dist/esm/icons/file-text.mjs:
13388
14998
  lucide-react/dist/esm/icons/image.mjs:
13389
14999
  lucide-react/dist/esm/icons/layout-grid.mjs:
13390
15000
  lucide-react/dist/esm/icons/link-2.mjs:
@@ -13392,16 +15002,20 @@ lucide-react/dist/esm/icons/list-filter.mjs:
13392
15002
  lucide-react/dist/esm/icons/map.mjs:
13393
15003
  lucide-react/dist/esm/icons/maximize-2.mjs:
13394
15004
  lucide-react/dist/esm/icons/monitor.mjs:
15005
+ lucide-react/dist/esm/icons/moon.mjs:
13395
15006
  lucide-react/dist/esm/icons/pencil.mjs:
13396
15007
  lucide-react/dist/esm/icons/rectangle-horizontal.mjs:
13397
15008
  lucide-react/dist/esm/icons/refresh-cw.mjs:
13398
15009
  lucide-react/dist/esm/icons/ruler.mjs:
13399
15010
  lucide-react/dist/esm/icons/scan.mjs:
15011
+ lucide-react/dist/esm/icons/search.mjs:
13400
15012
  lucide-react/dist/esm/icons/settings.mjs:
13401
15013
  lucide-react/dist/esm/icons/smartphone.mjs:
13402
15014
  lucide-react/dist/esm/icons/square-mouse-pointer.mjs:
13403
15015
  lucide-react/dist/esm/icons/sticky-note.mjs:
15016
+ lucide-react/dist/esm/icons/sun.mjs:
13404
15017
  lucide-react/dist/esm/icons/upload.mjs:
15018
+ lucide-react/dist/esm/icons/user-round.mjs:
13405
15019
  lucide-react/dist/esm/icons/x.mjs:
13406
15020
  lucide-react/dist/esm/lucide-react.mjs:
13407
15021
  (**