@in-the-loop-labs/pair-review 2.6.2 → 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/bin/git-diff-lines +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/skills/analyze/scripts/git-diff-lines +1 -1
- package/public/css/pr.css +201 -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 +22 -16
- package/src/ai/cursor-agent-provider.js +21 -12
- package/src/chat/prompt-builder.js +3 -3
- 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/git/worktree.js +3 -2
- 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 +57 -6
- package/src/routes/analyses.js +73 -10
- package/src/routes/chat.js +33 -0
- package/src/routes/config.js +1 -0
- package/src/routes/github-collections.js +2 -2
- package/src/routes/local.js +734 -68
- package/src/routes/mcp.js +20 -10
- package/src/routes/pr.js +92 -14
- 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/src/utils/diff-file-list.js +1 -1
package/bin/git-diff-lines
CHANGED
|
@@ -68,7 +68,7 @@ function executeGitDiff(args, cwd = null) {
|
|
|
68
68
|
if (cwd) {
|
|
69
69
|
spawnOptions.cwd = cwd;
|
|
70
70
|
}
|
|
71
|
-
const gitProcess = spawn('git', ['diff', ...args], spawnOptions);
|
|
71
|
+
const gitProcess = spawn('git', ['diff', '--no-ext-diff', ...args], spawnOptions);
|
|
72
72
|
|
|
73
73
|
let stdout = '';
|
|
74
74
|
let stderr = '';
|
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",
|
|
@@ -84,7 +84,7 @@ GIT_CMD=(git)
|
|
|
84
84
|
if [[ -n "$GIT_CWD" ]]; then
|
|
85
85
|
GIT_CMD+=(-C "$GIT_CWD")
|
|
86
86
|
fi
|
|
87
|
-
GIT_CMD+=(diff)
|
|
87
|
+
GIT_CMD+=(diff --no-ext-diff)
|
|
88
88
|
# Guard: expanding an empty array with set -u fails in Bash < 4.4
|
|
89
89
|
if [[ ${#GIT_ARGS[@]} -gt 0 ]]; then
|
|
90
90
|
GIT_CMD+=("${GIT_ARGS[@]}")
|
package/public/css/pr.css
CHANGED
|
@@ -1587,6 +1587,13 @@
|
|
|
1587
1587
|
color: #f85149;
|
|
1588
1588
|
}
|
|
1589
1589
|
|
|
1590
|
+
/* When collapsed, the wrapper is only as tall as the header, so the header's
|
|
1591
|
+
sticky positioning can't push it below the toolbar. Adding scroll-margin
|
|
1592
|
+
ensures scrollIntoView() lands the header below the sticky toolbar. */
|
|
1593
|
+
.d2h-file-wrapper.collapsed {
|
|
1594
|
+
scroll-margin-top: var(--toolbar-height, 0px);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1590
1597
|
/* Hide diff content when collapsed */
|
|
1591
1598
|
.d2h-file-wrapper.collapsed .d2h-file-body,
|
|
1592
1599
|
.d2h-file-wrapper.collapsed .d2h-diff-table {
|
|
@@ -2689,6 +2696,36 @@ tr.newly-expanded .d2h-code-line-ctn {
|
|
|
2689
2696
|
justify-content: center;
|
|
2690
2697
|
}
|
|
2691
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
|
+
|
|
2692
2729
|
/* Dialogs spawned from within other modals need higher z-index */
|
|
2693
2730
|
#text-input-dialog,
|
|
2694
2731
|
#confirm-dialog {
|
|
@@ -7237,6 +7274,7 @@ body.resizing * {
|
|
|
7237
7274
|
.diff-options-popover {
|
|
7238
7275
|
position: fixed;
|
|
7239
7276
|
z-index: 1100;
|
|
7277
|
+
min-width: 320px;
|
|
7240
7278
|
background: var(--color-bg-primary);
|
|
7241
7279
|
border: 1px solid var(--color-border-primary);
|
|
7242
7280
|
border-radius: 8px;
|
|
@@ -7308,6 +7346,150 @@ body.resizing * {
|
|
|
7308
7346
|
background: rgba(88, 166, 255, 0.15);
|
|
7309
7347
|
}
|
|
7310
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
|
+
|
|
7311
7493
|
.ai-panel-header {
|
|
7312
7494
|
display: flex;
|
|
7313
7495
|
align-items: center;
|
|
@@ -11825,6 +12007,25 @@ body.resizing * {
|
|
|
11825
12007
|
30% { opacity: 1; transform: scale(1); }
|
|
11826
12008
|
}
|
|
11827
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
|
+
|
|
11828
12029
|
/* Blinking cursor during streaming */
|
|
11829
12030
|
.chat-panel__cursor {
|
|
11830
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
|
}
|