@in-the-loop-labs/pair-review 2.6.3 → 2.7.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.
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/public/css/pr.css +194 -0
- package/public/index.html +168 -3
- package/public/js/components/AIPanel.js +16 -2
- package/public/js/components/ChatPanel.js +41 -6
- package/public/js/components/ConfirmDialog.js +21 -2
- package/public/js/components/CouncilProgressModal.js +13 -0
- package/public/js/components/DiffOptionsDropdown.js +410 -23
- package/public/js/components/SuggestionNavigator.js +12 -5
- package/public/js/components/TabTitle.js +96 -0
- package/public/js/components/Toast.js +6 -0
- package/public/js/index.js +648 -43
- package/public/js/local.js +569 -76
- package/public/js/modules/analysis-history.js +3 -2
- package/public/js/modules/comment-manager.js +5 -0
- package/public/js/modules/comment-minimizer.js +304 -0
- package/public/js/pr.js +82 -6
- package/public/local.html +14 -0
- package/public/pr.html +3 -0
- package/src/ai/analyzer.js +17 -11
- package/src/config.js +2 -0
- package/src/database.js +590 -39
- package/src/git/base-branch.js +173 -0
- package/src/git/sha-abbrev.js +35 -0
- package/src/github/client.js +32 -1
- package/src/hooks/hook-runner.js +100 -0
- package/src/hooks/payloads.js +212 -0
- package/src/local-review.js +468 -129
- package/src/local-scope.js +58 -0
- package/src/main.js +55 -4
- package/src/routes/analyses.js +73 -10
- package/src/routes/chat.js +33 -0
- package/src/routes/config.js +1 -0
- package/src/routes/local.js +734 -68
- package/src/routes/mcp.js +20 -10
- package/src/routes/pr.js +90 -12
- package/src/routes/setup.js +1 -0
- package/src/routes/worktrees.js +212 -148
- package/src/server.js +30 -0
- package/src/setup/local-setup.js +46 -5
- package/src/setup/pr-setup.js +28 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pair-review",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "pair-review app integration — Open PRs and local changes in the pair-review web UI, run server-side AI analysis, and address review feedback. Requires the pair-review MCP server.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "in-the-loop-labs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-critic",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "AI-powered code review analysis — Run three-level AI analysis and implement-review-fix loops directly in your coding agent. Works standalone, no server required.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "in-the-loop-labs",
|
package/public/css/pr.css
CHANGED
|
@@ -2696,6 +2696,36 @@ tr.newly-expanded .d2h-code-line-ctn {
|
|
|
2696
2696
|
justify-content: center;
|
|
2697
2697
|
}
|
|
2698
2698
|
|
|
2699
|
+
/* When a secondary button is visible (3 buttons), widen and stack vertically.
|
|
2700
|
+
column-reverse puts Confirm (primary) on top, Cancel on bottom. */
|
|
2701
|
+
.confirm-dialog-container.has-secondary {
|
|
2702
|
+
min-width: 440px;
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
.confirm-dialog-container.has-secondary .modal-footer {
|
|
2706
|
+
flex-direction: column-reverse;
|
|
2707
|
+
align-items: stretch;
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
.confirm-dialog-container.has-secondary .modal-footer .btn {
|
|
2711
|
+
display: flex;
|
|
2712
|
+
flex-direction: column;
|
|
2713
|
+
align-items: center;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
/* Button description subtitles */
|
|
2717
|
+
.modal-footer .btn .btn-label {
|
|
2718
|
+
display: block;
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
.modal-footer .btn .btn-desc {
|
|
2722
|
+
display: block;
|
|
2723
|
+
font-size: 12px;
|
|
2724
|
+
font-weight: normal;
|
|
2725
|
+
opacity: 0.7;
|
|
2726
|
+
margin-top: 2px;
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2699
2729
|
/* Dialogs spawned from within other modals need higher z-index */
|
|
2700
2730
|
#text-input-dialog,
|
|
2701
2731
|
#confirm-dialog {
|
|
@@ -7244,6 +7274,7 @@ body.resizing * {
|
|
|
7244
7274
|
.diff-options-popover {
|
|
7245
7275
|
position: fixed;
|
|
7246
7276
|
z-index: 1100;
|
|
7277
|
+
min-width: 320px;
|
|
7247
7278
|
background: var(--color-bg-primary);
|
|
7248
7279
|
border: 1px solid var(--color-border-primary);
|
|
7249
7280
|
border-radius: 8px;
|
|
@@ -7315,6 +7346,150 @@ body.resizing * {
|
|
|
7315
7346
|
background: rgba(88, 166, 255, 0.15);
|
|
7316
7347
|
}
|
|
7317
7348
|
|
|
7349
|
+
/* --------------------------------------------------------------------------
|
|
7350
|
+
Comment Minimize Mode
|
|
7351
|
+
-------------------------------------------------------------------------- */
|
|
7352
|
+
|
|
7353
|
+
/* When minimize mode is active, hide all inline comment and suggestion rows */
|
|
7354
|
+
.comments-minimized .user-comment-row,
|
|
7355
|
+
.comments-minimized .ai-suggestion-row {
|
|
7356
|
+
display: none;
|
|
7357
|
+
}
|
|
7358
|
+
|
|
7359
|
+
/* Per-line expansion override — clicking an indicator reveals that line's rows */
|
|
7360
|
+
.comments-minimized .user-comment-row.comment-expanded,
|
|
7361
|
+
.comments-minimized .ai-suggestion-row.comment-expanded {
|
|
7362
|
+
display: table-row;
|
|
7363
|
+
}
|
|
7364
|
+
|
|
7365
|
+
/* Indicator button on the right edge of diff code cells */
|
|
7366
|
+
.comment-indicator {
|
|
7367
|
+
position: absolute;
|
|
7368
|
+
right: 4px;
|
|
7369
|
+
top: 50%;
|
|
7370
|
+
transform: translateY(-50%);
|
|
7371
|
+
display: inline-flex;
|
|
7372
|
+
align-items: center;
|
|
7373
|
+
gap: 3px;
|
|
7374
|
+
padding: 2px 6px;
|
|
7375
|
+
border: 1px solid var(--color-border-primary);
|
|
7376
|
+
border-radius: 12px;
|
|
7377
|
+
background: var(--color-bg-secondary);
|
|
7378
|
+
cursor: pointer;
|
|
7379
|
+
font-size: 11px;
|
|
7380
|
+
line-height: 1;
|
|
7381
|
+
color: var(--color-text-secondary);
|
|
7382
|
+
opacity: 1;
|
|
7383
|
+
transition: background 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
|
|
7384
|
+
z-index: 2; /* must be below sticky file headers (z-index: 4) */
|
|
7385
|
+
}
|
|
7386
|
+
|
|
7387
|
+
.comment-indicator:hover {
|
|
7388
|
+
background: var(--color-bg-tertiary);
|
|
7389
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
7390
|
+
}
|
|
7391
|
+
|
|
7392
|
+
.comment-indicator.expanded {
|
|
7393
|
+
border-width: 2px;
|
|
7394
|
+
padding: 1px 5px; /* compensate for thicker border */
|
|
7395
|
+
}
|
|
7396
|
+
|
|
7397
|
+
.comment-indicator .indicator-icon {
|
|
7398
|
+
display: inline-flex;
|
|
7399
|
+
align-items: center;
|
|
7400
|
+
}
|
|
7401
|
+
|
|
7402
|
+
.comment-indicator .indicator-icon svg {
|
|
7403
|
+
display: block;
|
|
7404
|
+
}
|
|
7405
|
+
|
|
7406
|
+
.comment-indicator .indicator-user {
|
|
7407
|
+
color: var(--comment-primary, #8250df);
|
|
7408
|
+
}
|
|
7409
|
+
|
|
7410
|
+
.comment-indicator .indicator-adopted {
|
|
7411
|
+
color: var(--comment-primary, #8250df);
|
|
7412
|
+
}
|
|
7413
|
+
|
|
7414
|
+
.comment-indicator:has(.indicator-user),
|
|
7415
|
+
.comment-indicator:has(.indicator-adopted) {
|
|
7416
|
+
border-color: var(--comment-primary, #8250df);
|
|
7417
|
+
background: rgba(130, 80, 223, 0.06);
|
|
7418
|
+
}
|
|
7419
|
+
|
|
7420
|
+
.comment-indicator:has(.indicator-user):hover,
|
|
7421
|
+
.comment-indicator:has(.indicator-adopted):hover {
|
|
7422
|
+
background: rgba(130, 80, 223, 0.12);
|
|
7423
|
+
box-shadow: 0 1px 3px rgba(130, 80, 223, 0.15);
|
|
7424
|
+
}
|
|
7425
|
+
|
|
7426
|
+
.comment-indicator .indicator-ai {
|
|
7427
|
+
color: var(--ai-accent, #d97706);
|
|
7428
|
+
}
|
|
7429
|
+
|
|
7430
|
+
.comment-indicator:has(.indicator-ai) {
|
|
7431
|
+
border-color: var(--ai-accent, #d97706);
|
|
7432
|
+
background: rgba(217, 119, 6, 0.06);
|
|
7433
|
+
}
|
|
7434
|
+
|
|
7435
|
+
.comment-indicator:has(.indicator-ai):hover {
|
|
7436
|
+
background: rgba(217, 119, 6, 0.12);
|
|
7437
|
+
box-shadow: 0 1px 3px rgba(217, 119, 6, 0.15);
|
|
7438
|
+
}
|
|
7439
|
+
|
|
7440
|
+
.comment-indicator .indicator-count {
|
|
7441
|
+
font-weight: 600;
|
|
7442
|
+
font-size: 10px;
|
|
7443
|
+
min-width: 14px;
|
|
7444
|
+
text-align: center;
|
|
7445
|
+
}
|
|
7446
|
+
|
|
7447
|
+
/* Dark theme overrides */
|
|
7448
|
+
[data-theme="dark"] .comment-indicator {
|
|
7449
|
+
background: var(--color-bg-tertiary);
|
|
7450
|
+
border-color: var(--color-border-secondary);
|
|
7451
|
+
}
|
|
7452
|
+
|
|
7453
|
+
[data-theme="dark"] .comment-indicator:hover {
|
|
7454
|
+
background: var(--color-bg-primary);
|
|
7455
|
+
}
|
|
7456
|
+
|
|
7457
|
+
[data-theme="dark"] .comment-indicator:has(.indicator-user),
|
|
7458
|
+
[data-theme="dark"] .comment-indicator:has(.indicator-adopted) {
|
|
7459
|
+
border-color: var(--comment-primary, #a371f7);
|
|
7460
|
+
background: rgba(163, 113, 247, 0.1);
|
|
7461
|
+
}
|
|
7462
|
+
|
|
7463
|
+
[data-theme="dark"] .comment-indicator .indicator-user,
|
|
7464
|
+
[data-theme="dark"] .comment-indicator .indicator-adopted {
|
|
7465
|
+
color: var(--comment-primary, #a371f7);
|
|
7466
|
+
}
|
|
7467
|
+
|
|
7468
|
+
[data-theme="dark"] .comment-indicator:has(.indicator-user):hover,
|
|
7469
|
+
[data-theme="dark"] .comment-indicator:has(.indicator-adopted):hover {
|
|
7470
|
+
background: rgba(163, 113, 247, 0.18);
|
|
7471
|
+
box-shadow: 0 1px 3px rgba(163, 113, 247, 0.25);
|
|
7472
|
+
}
|
|
7473
|
+
|
|
7474
|
+
[data-theme="dark"] .comment-indicator:has(.indicator-ai) {
|
|
7475
|
+
border-color: var(--color-accent-ai, #fbbf24);
|
|
7476
|
+
background: rgba(251, 191, 36, 0.1);
|
|
7477
|
+
}
|
|
7478
|
+
|
|
7479
|
+
[data-theme="dark"] .comment-indicator .indicator-ai {
|
|
7480
|
+
color: var(--color-accent-ai, #fbbf24);
|
|
7481
|
+
}
|
|
7482
|
+
|
|
7483
|
+
[data-theme="dark"] .comment-indicator:has(.indicator-ai):hover {
|
|
7484
|
+
background: rgba(251, 191, 36, 0.18);
|
|
7485
|
+
box-shadow: 0 1px 3px rgba(251, 191, 36, 0.25);
|
|
7486
|
+
}
|
|
7487
|
+
|
|
7488
|
+
[data-theme="dark"] .comment-indicator.expanded {
|
|
7489
|
+
border-width: 2px;
|
|
7490
|
+
padding: 1px 5px;
|
|
7491
|
+
}
|
|
7492
|
+
|
|
7318
7493
|
.ai-panel-header {
|
|
7319
7494
|
display: flex;
|
|
7320
7495
|
align-items: center;
|
|
@@ -11832,6 +12007,25 @@ body.resizing * {
|
|
|
11832
12007
|
30% { opacity: 1; transform: scale(1); }
|
|
11833
12008
|
}
|
|
11834
12009
|
|
|
12010
|
+
/* Loop logo spinner (easter egg: chat_spinner = "loop") */
|
|
12011
|
+
.chat-panel__loop-spinner {
|
|
12012
|
+
display: inline-flex;
|
|
12013
|
+
align-items: center;
|
|
12014
|
+
padding: 2px 0;
|
|
12015
|
+
}
|
|
12016
|
+
|
|
12017
|
+
.chat-panel__loop-spinner svg {
|
|
12018
|
+
width: 20px;
|
|
12019
|
+
height: 20px;
|
|
12020
|
+
color: var(--color-accent-ai, #8b5cf6);
|
|
12021
|
+
animation: loop-spin 1.4s linear infinite;
|
|
12022
|
+
}
|
|
12023
|
+
|
|
12024
|
+
@keyframes loop-spin {
|
|
12025
|
+
0% { transform: rotate(0deg); }
|
|
12026
|
+
100% { transform: rotate(360deg); }
|
|
12027
|
+
}
|
|
12028
|
+
|
|
11835
12029
|
/* Blinking cursor during streaming */
|
|
11836
12030
|
.chat-panel__cursor {
|
|
11837
12031
|
display: inline-block;
|
package/public/index.html
CHANGED
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
|
|
62
62
|
--color-accent-primary: #0969da;
|
|
63
63
|
--color-accent-hover: #0860ca;
|
|
64
|
+
--color-accent-subtle: rgba(9, 105, 218, 0.04);
|
|
64
65
|
--color-accent-emphasis: #238636;
|
|
65
66
|
--color-accent-emphasis-hover: #2ea043;
|
|
66
67
|
|
|
@@ -94,6 +95,7 @@
|
|
|
94
95
|
|
|
95
96
|
--color-accent-primary: #58a6ff;
|
|
96
97
|
--color-accent-hover: #1f6feb;
|
|
98
|
+
--color-accent-subtle: rgba(56, 139, 253, 0.06);
|
|
97
99
|
--color-accent-emphasis: #238636;
|
|
98
100
|
--color-accent-emphasis-hover: #2ea043;
|
|
99
101
|
|
|
@@ -687,7 +689,7 @@
|
|
|
687
689
|
margin-right: 4px;
|
|
688
690
|
}
|
|
689
691
|
|
|
690
|
-
.btn-delete-
|
|
692
|
+
.btn-delete-review,
|
|
691
693
|
.btn-repo-settings {
|
|
692
694
|
display: inline-flex;
|
|
693
695
|
align-items: center;
|
|
@@ -703,13 +705,13 @@
|
|
|
703
705
|
transition: all var(--transition-fast);
|
|
704
706
|
}
|
|
705
707
|
|
|
706
|
-
.btn-delete-
|
|
708
|
+
.btn-delete-review:hover {
|
|
707
709
|
background-color: rgba(208, 36, 47, 0.08);
|
|
708
710
|
border-color: rgba(208, 36, 47, 0.3);
|
|
709
711
|
color: var(--color-danger);
|
|
710
712
|
}
|
|
711
713
|
|
|
712
|
-
[data-theme="dark"] .btn-delete-
|
|
714
|
+
[data-theme="dark"] .btn-delete-review:hover {
|
|
713
715
|
background-color: rgba(248, 81, 73, 0.1);
|
|
714
716
|
border-color: rgba(248, 81, 73, 0.3);
|
|
715
717
|
}
|
|
@@ -1053,6 +1055,168 @@
|
|
|
1053
1055
|
color: var(--color-text-primary);
|
|
1054
1056
|
}
|
|
1055
1057
|
|
|
1058
|
+
/* ─── Selection Mode ─────────────────────────────────────── */
|
|
1059
|
+
|
|
1060
|
+
/* Select toggle button — matches .btn-refresh sizing */
|
|
1061
|
+
.btn-select-toggle {
|
|
1062
|
+
display: inline-flex;
|
|
1063
|
+
align-items: center;
|
|
1064
|
+
gap: 6px;
|
|
1065
|
+
padding: 4px 12px;
|
|
1066
|
+
font-family: var(--font-sans);
|
|
1067
|
+
font-size: 12px;
|
|
1068
|
+
font-weight: 500;
|
|
1069
|
+
color: var(--color-text-secondary);
|
|
1070
|
+
background: transparent;
|
|
1071
|
+
border: 1px solid var(--color-border-primary);
|
|
1072
|
+
border-radius: var(--radius-md);
|
|
1073
|
+
cursor: pointer;
|
|
1074
|
+
transition: all var(--transition-fast);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.btn-select-toggle:hover {
|
|
1078
|
+
background: var(--color-bg-secondary);
|
|
1079
|
+
color: var(--color-text-primary);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.btn-select-toggle.active {
|
|
1083
|
+
background: var(--color-accent-subtle);
|
|
1084
|
+
border-color: var(--color-accent-primary);
|
|
1085
|
+
color: var(--color-accent-primary);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/* Checkbox column */
|
|
1089
|
+
.col-select {
|
|
1090
|
+
width: 32px;
|
|
1091
|
+
text-align: center;
|
|
1092
|
+
padding: 6px 8px !important;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.col-select input[type="checkbox"] {
|
|
1096
|
+
width: 15px;
|
|
1097
|
+
height: 15px;
|
|
1098
|
+
cursor: pointer;
|
|
1099
|
+
accent-color: var(--color-accent-primary);
|
|
1100
|
+
vertical-align: middle;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/* Selected row highlight */
|
|
1104
|
+
.recent-reviews-table tbody tr.bulk-selected td {
|
|
1105
|
+
background-color: var(--color-accent-subtle);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/* Hide per-row delete buttons in selection mode */
|
|
1109
|
+
.selection-mode .btn-delete-review,
|
|
1110
|
+
.selection-mode .btn-delete-session {
|
|
1111
|
+
display: none;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/* Inline bulk action controls — appear next to Select button */
|
|
1115
|
+
.bulk-inline-actions {
|
|
1116
|
+
display: inline-flex;
|
|
1117
|
+
align-items: center;
|
|
1118
|
+
gap: 8px;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
.bulk-action-count {
|
|
1122
|
+
font-size: 12px;
|
|
1123
|
+
font-weight: 500;
|
|
1124
|
+
color: var(--color-text-secondary);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
.bulk-action-buttons {
|
|
1128
|
+
display: inline-flex;
|
|
1129
|
+
gap: 6px;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.btn-bulk-delete {
|
|
1133
|
+
padding: 4px 12px;
|
|
1134
|
+
font-size: 12px;
|
|
1135
|
+
font-weight: 500;
|
|
1136
|
+
background: var(--color-danger);
|
|
1137
|
+
color: #ffffff;
|
|
1138
|
+
border: none;
|
|
1139
|
+
border-radius: var(--radius-sm);
|
|
1140
|
+
cursor: pointer;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
.btn-bulk-delete:hover:not(:disabled) {
|
|
1144
|
+
background: var(--color-danger-hover);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
.btn-bulk-delete:disabled {
|
|
1148
|
+
opacity: 0.4;
|
|
1149
|
+
cursor: default;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
.btn-bulk-open,
|
|
1153
|
+
.btn-bulk-analyze {
|
|
1154
|
+
padding: 4px 12px;
|
|
1155
|
+
font-size: 12px;
|
|
1156
|
+
font-weight: 500;
|
|
1157
|
+
background: var(--color-accent-primary);
|
|
1158
|
+
color: #ffffff;
|
|
1159
|
+
border: none;
|
|
1160
|
+
border-radius: var(--radius-sm);
|
|
1161
|
+
cursor: pointer;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
.btn-bulk-open:hover:not(:disabled),
|
|
1165
|
+
.btn-bulk-analyze:hover:not(:disabled) {
|
|
1166
|
+
opacity: 0.9;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
.btn-bulk-open:disabled,
|
|
1170
|
+
.btn-bulk-analyze:disabled {
|
|
1171
|
+
opacity: 0.4;
|
|
1172
|
+
cursor: default;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
.btn-bulk-cancel {
|
|
1176
|
+
padding: 4px 12px;
|
|
1177
|
+
font-size: 12px;
|
|
1178
|
+
font-weight: 500;
|
|
1179
|
+
background: transparent;
|
|
1180
|
+
color: var(--color-text-secondary);
|
|
1181
|
+
border: 1px solid var(--color-border-primary);
|
|
1182
|
+
border-radius: var(--radius-sm);
|
|
1183
|
+
cursor: pointer;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
.btn-bulk-cancel:hover {
|
|
1187
|
+
background: var(--color-bg-secondary);
|
|
1188
|
+
color: var(--color-text-primary);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/* Confirmation state — hide normal action buttons, show confirm buttons */
|
|
1192
|
+
.bulk-inline-actions .bulk-confirm-buttons {
|
|
1193
|
+
display: none;
|
|
1194
|
+
gap: 6px;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.bulk-inline-actions.confirming .bulk-action-buttons {
|
|
1198
|
+
display: none;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
.bulk-inline-actions.confirming .bulk-confirm-buttons {
|
|
1202
|
+
display: inline-flex;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
.bulk-inline-actions.confirming > .btn-bulk-cancel {
|
|
1206
|
+
display: none;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/* Select mode header row (between form and table, for PR/Local tabs) */
|
|
1210
|
+
.select-mode-header {
|
|
1211
|
+
display: none;
|
|
1212
|
+
justify-content: flex-end;
|
|
1213
|
+
padding: 0 0 8px 0;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
.select-mode-header.visible {
|
|
1217
|
+
display: flex;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1056
1220
|
/* Start review section spacing inside unified tabs */
|
|
1057
1221
|
.tab-pane .start-review-section {
|
|
1058
1222
|
max-width: 100%;
|
|
@@ -1266,6 +1430,7 @@
|
|
|
1266
1430
|
</div>
|
|
1267
1431
|
</div>
|
|
1268
1432
|
|
|
1433
|
+
<script src="/js/components/Toast.js"></script>
|
|
1269
1434
|
<script src="/js/index.js"></script>
|
|
1270
1435
|
</body>
|
|
1271
1436
|
</html>
|
|
@@ -1000,7 +1000,14 @@ class AIPanel {
|
|
|
1000
1000
|
}
|
|
1001
1001
|
|
|
1002
1002
|
if (targetSuggestion) {
|
|
1003
|
-
|
|
1003
|
+
const minimizer = window.prManager?.commentMinimizer;
|
|
1004
|
+
if (minimizer?.active) {
|
|
1005
|
+
// Comments are minimized — scroll to the parent diff line instead
|
|
1006
|
+
const diffRow = minimizer.findDiffRowFor(targetSuggestion);
|
|
1007
|
+
(diffRow || targetSuggestion).scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1008
|
+
} else {
|
|
1009
|
+
targetSuggestion.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1010
|
+
}
|
|
1004
1011
|
targetSuggestion.classList.add('current-suggestion');
|
|
1005
1012
|
setTimeout(() => targetSuggestion.classList.remove('current-suggestion'), 2000);
|
|
1006
1013
|
}
|
|
@@ -1061,7 +1068,14 @@ class AIPanel {
|
|
|
1061
1068
|
}
|
|
1062
1069
|
|
|
1063
1070
|
if (targetElement) {
|
|
1064
|
-
|
|
1071
|
+
const minimizer = window.prManager?.commentMinimizer;
|
|
1072
|
+
if (minimizer?.active && !isFileLevel) {
|
|
1073
|
+
// Comments are minimized — scroll to the parent diff line instead
|
|
1074
|
+
const diffRow = minimizer.findDiffRowFor(targetElement);
|
|
1075
|
+
(diffRow || targetElement).scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1076
|
+
} else {
|
|
1077
|
+
targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1078
|
+
}
|
|
1065
1079
|
// Add highlight effect
|
|
1066
1080
|
const commentDiv = isFileLevel ? targetElement : targetElement.querySelector('.user-comment');
|
|
1067
1081
|
if (commentDiv) {
|
|
@@ -10,6 +10,13 @@ const DISMISS_ICON = `<svg viewBox="0 0 16 16" fill="currentColor" width="12" he
|
|
|
10
10
|
/** Pixel threshold for considering the user "near the bottom" of the messages container. */
|
|
11
11
|
const NEAR_BOTTOM_THRESHOLD = 80;
|
|
12
12
|
|
|
13
|
+
const LOOP_SPINNER_HTML = `<span class="chat-panel__loop-spinner"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" width="20" height="20"><path transform="rotate(-50 12 12)" d="M18.178 8c5.096 0 5.096 8 0 8-5.095 0-7.133-8-12.356-8-5.096 0-5.096 8 0 8 5.223 0 7.26-8 12.356-8z"/></svg></span>`;
|
|
14
|
+
const DOTS_SPINNER_HTML = '<span class="chat-panel__typing-indicator"><span></span><span></span><span></span></span>';
|
|
15
|
+
|
|
16
|
+
function getChatSpinnerHTML() {
|
|
17
|
+
return window.__pairReview?.chatSpinner === 'loop' ? LOOP_SPINNER_HTML : DOTS_SPINNER_HTML;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
class ChatPanel {
|
|
14
21
|
constructor(containerId) {
|
|
15
22
|
this.containerId = containerId;
|
|
@@ -24,6 +31,7 @@ class ChatPanel {
|
|
|
24
31
|
this._streamingContent = '';
|
|
25
32
|
this._pendingContext = [];
|
|
26
33
|
this._pendingContextData = [];
|
|
34
|
+
this._pendingDiffStateNotifications = [];
|
|
27
35
|
this._contextSource = null; // 'suggestion' or 'user' — set when opened with context
|
|
28
36
|
this._contextItemId = null; // suggestion ID or comment ID from context
|
|
29
37
|
this._contextLineMeta = null; // { file, line_start, line_end } — set when opened with line context
|
|
@@ -641,6 +649,7 @@ class ChatPanel {
|
|
|
641
649
|
this._streamingContent = '';
|
|
642
650
|
this._pendingContext = [];
|
|
643
651
|
this._pendingContextData = [];
|
|
652
|
+
this._pendingDiffStateNotifications = [];
|
|
644
653
|
this._contextSource = null;
|
|
645
654
|
this._contextItemId = null;
|
|
646
655
|
this._contextLineMeta = null;
|
|
@@ -1003,6 +1012,7 @@ class ChatPanel {
|
|
|
1003
1012
|
this._streamingContent = '';
|
|
1004
1013
|
this._pendingContext = [];
|
|
1005
1014
|
this._pendingContextData = [];
|
|
1015
|
+
this._pendingDiffStateNotifications = [];
|
|
1006
1016
|
this._contextSource = null;
|
|
1007
1017
|
this._contextItemId = null;
|
|
1008
1018
|
this._contextLineMeta = null;
|
|
@@ -1233,10 +1243,22 @@ class ChatPanel {
|
|
|
1233
1243
|
|
|
1234
1244
|
// Build the API payload — may include pending context from "Ask about this"
|
|
1235
1245
|
const payload = { content };
|
|
1246
|
+
|
|
1247
|
+
// Snapshot diff-state queue for error recovery (invisible to user, no UI cards)
|
|
1248
|
+
const savedDiffState = this._pendingDiffStateNotifications.slice();
|
|
1249
|
+
let diffStatePrefix = '';
|
|
1250
|
+
if (this._pendingDiffStateNotifications.length > 0) {
|
|
1251
|
+
diffStatePrefix = '[Diff State Update]\n' + this._pendingDiffStateNotifications.join('\n');
|
|
1252
|
+
this._pendingDiffStateNotifications = [];
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1236
1255
|
const savedContext = this._pendingContext;
|
|
1237
1256
|
const savedContextData = this._pendingContextData;
|
|
1238
1257
|
if (this._pendingContext.length > 0) {
|
|
1239
|
-
|
|
1258
|
+
const userContext = this._pendingContext.join('\n\n');
|
|
1259
|
+
payload.context = diffStatePrefix
|
|
1260
|
+
? diffStatePrefix + '\n\n' + userContext
|
|
1261
|
+
: userContext;
|
|
1240
1262
|
payload.contextData = this._pendingContextData;
|
|
1241
1263
|
this._pendingContext = [];
|
|
1242
1264
|
this._pendingContextData = [];
|
|
@@ -1248,6 +1270,8 @@ class ChatPanel {
|
|
|
1248
1270
|
if (btn) btn.remove();
|
|
1249
1271
|
delete card.dataset.contextIndex;
|
|
1250
1272
|
});
|
|
1273
|
+
} else if (diffStatePrefix) {
|
|
1274
|
+
payload.context = diffStatePrefix;
|
|
1251
1275
|
}
|
|
1252
1276
|
|
|
1253
1277
|
// Lock analysis context card (not indexed, handled separately from pending context)
|
|
@@ -1311,6 +1335,7 @@ class ChatPanel {
|
|
|
1311
1335
|
// Restore pending context so it's not lost
|
|
1312
1336
|
this._pendingContext = savedContext;
|
|
1313
1337
|
this._pendingContextData = savedContextData;
|
|
1338
|
+
this._pendingDiffStateNotifications = [...savedDiffState, ...this._pendingDiffStateNotifications];
|
|
1314
1339
|
// Restore removability on context cards that were locked before the failed send
|
|
1315
1340
|
this._restoreRemovableCards();
|
|
1316
1341
|
console.error('[ChatPanel] Error sending message:', error);
|
|
@@ -1319,6 +1344,16 @@ class ChatPanel {
|
|
|
1319
1344
|
}
|
|
1320
1345
|
}
|
|
1321
1346
|
|
|
1347
|
+
/**
|
|
1348
|
+
* Queue an invisible diff-state notification for the chat agent.
|
|
1349
|
+
* Unlike _pendingContext, these do NOT render UI cards and survive panel close.
|
|
1350
|
+
* Drained into the context parameter on the next sendMessage() call.
|
|
1351
|
+
* @param {string} message - Description of the diff state change
|
|
1352
|
+
*/
|
|
1353
|
+
queueDiffStateNotification(message) {
|
|
1354
|
+
this._pendingDiffStateNotifications.push(message);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1322
1357
|
/**
|
|
1323
1358
|
* Store pending context and render a compact context card in the UI.
|
|
1324
1359
|
* Called when the user clicks "Ask about this" on a suggestion.
|
|
@@ -2409,7 +2444,7 @@ class ChatPanel {
|
|
|
2409
2444
|
|
|
2410
2445
|
const bubble = document.createElement('div');
|
|
2411
2446
|
bubble.className = 'chat-panel__bubble';
|
|
2412
|
-
bubble.innerHTML =
|
|
2447
|
+
bubble.innerHTML = getChatSpinnerHTML();
|
|
2413
2448
|
|
|
2414
2449
|
msgEl.appendChild(bubble);
|
|
2415
2450
|
this.messagesEl.appendChild(msgEl);
|
|
@@ -2641,10 +2676,10 @@ class ChatPanel {
|
|
|
2641
2676
|
// Don't add duplicate
|
|
2642
2677
|
if (streamingMsg.querySelector('.chat-panel__thinking')) return;
|
|
2643
2678
|
|
|
2644
|
-
// Don't add if the bubble still has its initial
|
|
2645
|
-
// The bubble's own
|
|
2679
|
+
// Don't add if the bubble still has its initial spinner (no content yet).
|
|
2680
|
+
// The bubble's own indicator is sufficient — adding a second would show two.
|
|
2646
2681
|
const bubble = streamingMsg.querySelector('.chat-panel__bubble');
|
|
2647
|
-
if (bubble && bubble.querySelector('.chat-panel__typing-indicator')) return;
|
|
2682
|
+
if (bubble && (bubble.querySelector('.chat-panel__typing-indicator') || bubble.querySelector('.chat-panel__loop-spinner'))) return;
|
|
2648
2683
|
|
|
2649
2684
|
// Remove the cursor — the thinking indicator replaces it as the "working" signal.
|
|
2650
2685
|
// When new text arrives, updateStreamingMessage() will re-add the cursor naturally.
|
|
@@ -2653,7 +2688,7 @@ class ChatPanel {
|
|
|
2653
2688
|
|
|
2654
2689
|
const indicator = document.createElement('div');
|
|
2655
2690
|
indicator.className = 'chat-panel__thinking';
|
|
2656
|
-
indicator.innerHTML =
|
|
2691
|
+
indicator.innerHTML = getChatSpinnerHTML();
|
|
2657
2692
|
streamingMsg.appendChild(indicator);
|
|
2658
2693
|
this.scrollToBottom();
|
|
2659
2694
|
}
|
|
@@ -133,10 +133,20 @@ class ConfirmDialog {
|
|
|
133
133
|
messageElement.textContent = options.message || 'Are you sure?';
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
// Helper: set button label + optional description subtitle
|
|
137
|
+
const setBtnContent = (btn, label, description) => {
|
|
138
|
+
if (!btn) return;
|
|
139
|
+
if (description) {
|
|
140
|
+
btn.innerHTML = `<span class="btn-label">${label}</span><span class="btn-desc">${description}</span>`;
|
|
141
|
+
} else {
|
|
142
|
+
btn.textContent = label;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
136
146
|
// Set confirm button text and style
|
|
137
147
|
const confirmBtn = this.modal.querySelector('#confirm-dialog-btn');
|
|
138
148
|
if (confirmBtn) {
|
|
139
|
-
confirmBtn
|
|
149
|
+
setBtnContent(confirmBtn, options.confirmText || 'Confirm', options.confirmDesc);
|
|
140
150
|
// Remove previous style classes and add new one
|
|
141
151
|
confirmBtn.classList.remove('btn-primary', 'btn-secondary', 'btn-danger', 'btn-warning');
|
|
142
152
|
const confirmClass = options.confirmClass || 'btn-danger';
|
|
@@ -145,19 +155,28 @@ class ConfirmDialog {
|
|
|
145
155
|
|
|
146
156
|
// Set secondary button (optional 3rd button)
|
|
147
157
|
const secondaryBtn = this.modal.querySelector('#confirm-dialog-secondary-btn');
|
|
158
|
+
const container = this.modal.querySelector('.confirm-dialog-container');
|
|
148
159
|
if (secondaryBtn) {
|
|
149
160
|
if (options.secondaryText) {
|
|
150
|
-
secondaryBtn.
|
|
161
|
+
setBtnContent(secondaryBtn, options.secondaryText, options.secondaryDesc);
|
|
151
162
|
secondaryBtn.style.display = '';
|
|
152
163
|
// Remove previous style classes and add new one
|
|
153
164
|
secondaryBtn.classList.remove('btn-primary', 'btn-secondary', 'btn-danger', 'btn-warning');
|
|
154
165
|
const secondaryClass = options.secondaryClass || 'btn-secondary';
|
|
155
166
|
secondaryBtn.classList.add(secondaryClass);
|
|
167
|
+
if (container) container.classList.add('has-secondary');
|
|
156
168
|
} else {
|
|
157
169
|
secondaryBtn.style.display = 'none';
|
|
170
|
+
if (container) container.classList.remove('has-secondary');
|
|
158
171
|
}
|
|
159
172
|
}
|
|
160
173
|
|
|
174
|
+
// Set cancel button text (optional)
|
|
175
|
+
const cancelBtn = this.modal.querySelector('.modal-footer [data-action="cancel"]');
|
|
176
|
+
if (cancelBtn) {
|
|
177
|
+
setBtnContent(cancelBtn, options.cancelText || 'Cancel', options.cancelDesc);
|
|
178
|
+
}
|
|
179
|
+
|
|
161
180
|
// Store callbacks with promise resolution
|
|
162
181
|
this.onConfirm = () => {
|
|
163
182
|
if (options.onConfirm) {
|