@in-the-loop-labs/pair-review 1.6.2 → 2.0.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/README.md +77 -4
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/review-requests/SKILL.md +4 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/skills/analyze/SKILL.md +4 -3
- package/public/css/pr.css +1930 -114
- package/public/js/CONVENTIONS.md +16 -0
- package/public/js/components/AIPanel.js +66 -0
- package/public/js/components/AnalysisConfigModal.js +2 -2
- package/public/js/components/ChatPanel.js +2952 -0
- package/public/js/components/CouncilProgressModal.js +12 -16
- package/public/js/components/KeyboardShortcuts.js +3 -0
- package/public/js/components/PanelGroup.js +723 -0
- package/public/js/components/PreviewModal.js +3 -8
- package/public/js/index.js +8 -0
- package/public/js/local.js +17 -615
- package/public/js/modules/analysis-history.js +19 -68
- package/public/js/modules/comment-manager.js +57 -19
- package/public/js/modules/diff-context.js +176 -0
- package/public/js/modules/diff-renderer.js +30 -0
- package/public/js/modules/file-comment-manager.js +126 -105
- package/public/js/modules/file-list-merger.js +64 -0
- package/public/js/modules/panel-resizer.js +25 -6
- package/public/js/modules/suggestion-manager.js +40 -125
- package/public/js/pr.js +964 -159
- package/public/js/repo-settings.js +36 -6
- package/public/js/utils/category-emoji.js +44 -0
- package/public/js/utils/time.js +32 -0
- package/public/local.html +107 -70
- package/public/pr.html +107 -70
- package/public/repo-settings.html +32 -0
- package/src/ai/analyzer.js +5 -1
- package/src/ai/copilot-provider.js +39 -9
- package/src/ai/cursor-agent-provider.js +36 -7
- package/src/ai/gemini-provider.js +17 -4
- package/src/ai/prompts/config.js +7 -1
- package/src/ai/provider-availability.js +1 -1
- package/src/ai/provider.js +25 -37
- package/src/chat/CONVENTIONS.md +18 -0
- package/src/chat/pi-bridge.js +491 -0
- package/src/chat/prompt-builder.js +262 -0
- package/src/chat/session-manager.js +619 -0
- package/src/config.js +14 -0
- package/src/database.js +322 -15
- package/src/main.js +4 -17
- package/src/routes/analyses.js +721 -0
- package/src/routes/chat.js +655 -0
- package/src/routes/config.js +29 -8
- package/src/routes/context-files.js +223 -0
- package/src/routes/local.js +225 -1133
- package/src/routes/mcp.js +39 -30
- package/src/routes/pr.js +410 -52
- package/src/routes/reviews.js +1035 -0
- package/src/routes/shared.js +4 -29
- package/src/server.js +34 -12
- package/src/sse/review-events.js +46 -0
- package/src/utils/auto-context.js +88 -0
- package/src/utils/category-emoji.js +33 -0
- package/src/utils/diff-file-list.js +57 -0
- package/src/routes/analysis.js +0 -1600
- package/src/routes/comments.js +0 -534
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Frontend JS Conventions
|
|
2
|
+
|
|
3
|
+
## No nested interactive elements
|
|
4
|
+
|
|
5
|
+
HTML spec forbids nesting interactive content inside interactive elements (e.g. `<button>` inside `<button>`). Browsers enforce this by stripping the inner element from the DOM during parsing -- it will not render at all.
|
|
6
|
+
|
|
7
|
+
When you need a clickable element inside a `<button>`, use a `<span>` instead:
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<button class="outer-action">
|
|
11
|
+
Label
|
|
12
|
+
<span role="button" tabindex="-1" class="inner-action">X</span>
|
|
13
|
+
</button>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The `<span>` handler must call `event.stopPropagation()` to prevent the outer button's click from firing.
|
|
@@ -274,6 +274,7 @@ class AIPanel {
|
|
|
274
274
|
}
|
|
275
275
|
// Set CSS variable to 0 so width calculations don't reserve space
|
|
276
276
|
document.documentElement.style.setProperty('--ai-panel-width', '0px');
|
|
277
|
+
window.panelGroup?._onReviewVisibilityChanged(false);
|
|
277
278
|
}
|
|
278
279
|
|
|
279
280
|
expand() {
|
|
@@ -283,6 +284,7 @@ class AIPanel {
|
|
|
283
284
|
}
|
|
284
285
|
// Restore CSS variable from saved width or default
|
|
285
286
|
document.documentElement.style.setProperty('--ai-panel-width', `${this.getEffectivePanelWidth()}px`);
|
|
287
|
+
window.panelGroup?._onReviewVisibilityChanged(true);
|
|
286
288
|
}
|
|
287
289
|
|
|
288
290
|
/**
|
|
@@ -741,6 +743,44 @@ class AIPanel {
|
|
|
741
743
|
});
|
|
742
744
|
});
|
|
743
745
|
|
|
746
|
+
// Bind chat button events for AI suggestions and comments
|
|
747
|
+
this.findingsList.querySelectorAll('.quick-action-chat').forEach(btn => {
|
|
748
|
+
btn.addEventListener('click', (e) => {
|
|
749
|
+
e.stopPropagation(); // Prevent triggering item click
|
|
750
|
+
if (!window.chatPanel) return;
|
|
751
|
+
|
|
752
|
+
const findingId = btn.dataset.findingId ? parseInt(btn.dataset.findingId, 10) : null;
|
|
753
|
+
const commentId = btn.dataset.commentId ? parseInt(btn.dataset.commentId, 10) : null;
|
|
754
|
+
const file = btn.dataset.findingFile || '';
|
|
755
|
+
const title = btn.dataset.findingTitle || '';
|
|
756
|
+
|
|
757
|
+
// Build context from the finding data
|
|
758
|
+
let suggestionContext = { title, file };
|
|
759
|
+
|
|
760
|
+
if (findingId && this.findings) {
|
|
761
|
+
const finding = this.findings.find(f => f.id === findingId);
|
|
762
|
+
if (finding) {
|
|
763
|
+
suggestionContext = {
|
|
764
|
+
title: finding.title || title,
|
|
765
|
+
body: finding.body || '',
|
|
766
|
+
type: finding.type || '',
|
|
767
|
+
file: finding.file || file,
|
|
768
|
+
line_start: finding.line_start || null,
|
|
769
|
+
line_end: finding.line_end || null,
|
|
770
|
+
side: 'RIGHT',
|
|
771
|
+
reasoning: null
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
window.chatPanel.open({
|
|
777
|
+
reviewId: window.prManager?.currentPR?.id,
|
|
778
|
+
suggestionId: findingId ? String(findingId) : (commentId ? String(commentId) : undefined),
|
|
779
|
+
suggestionContext
|
|
780
|
+
});
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
|
|
744
784
|
// Restore active state if we have a current index
|
|
745
785
|
this.highlightCurrentItem();
|
|
746
786
|
}
|
|
@@ -1101,6 +1141,18 @@ class AIPanel {
|
|
|
1101
1141
|
`;
|
|
1102
1142
|
}
|
|
1103
1143
|
|
|
1144
|
+
// Chat button for active and dismissed findings (upper-right corner)
|
|
1145
|
+
let chatAction = '';
|
|
1146
|
+
if (finding.status !== 'adopted' && document.documentElement.getAttribute('data-chat') === 'available') {
|
|
1147
|
+
chatAction = `
|
|
1148
|
+
<div class="finding-chat-action">
|
|
1149
|
+
<button class="quick-action-btn quick-action-chat" data-finding-id="${finding.id}" data-finding-file="${finding.file || ''}" data-finding-title="${this.escapeHtml(title)}" title="Chat" aria-label="Chat about suggestion">
|
|
1150
|
+
<svg viewBox="0 0 16 16" fill="currentColor" width="12" height="12"><path d="M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z"/></svg>
|
|
1151
|
+
</button>
|
|
1152
|
+
</div>
|
|
1153
|
+
`;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1104
1156
|
return `
|
|
1105
1157
|
<div class="finding-item-wrapper">
|
|
1106
1158
|
<button class="finding-item finding-${type} ${statusClass}" data-index="${index}" data-id="${finding.id || ''}" data-file="${finding.file || ''}" data-line="${lineNum || ''}" data-item-type="finding" title="${fullLocation}">
|
|
@@ -1112,6 +1164,7 @@ class AIPanel {
|
|
|
1112
1164
|
</div>
|
|
1113
1165
|
</button>
|
|
1114
1166
|
${quickActions}
|
|
1167
|
+
${chatAction}
|
|
1115
1168
|
</div>
|
|
1116
1169
|
`;
|
|
1117
1170
|
}
|
|
@@ -1163,6 +1216,18 @@ class AIPanel {
|
|
|
1163
1216
|
`;
|
|
1164
1217
|
}
|
|
1165
1218
|
|
|
1219
|
+
// Chat button for active AI-originated comments
|
|
1220
|
+
let chatAction = '';
|
|
1221
|
+
if (!isDismissed && comment.parent_id && document.documentElement.getAttribute('data-chat') === 'available') {
|
|
1222
|
+
chatAction = `
|
|
1223
|
+
<div class="finding-chat-action">
|
|
1224
|
+
<button class="quick-action-btn quick-action-chat" data-comment-id="${comment.id}" data-finding-file="${comment.file || ''}" data-finding-title="${this.escapeHtml(title)}" title="Chat" aria-label="Chat about comment">
|
|
1225
|
+
<svg viewBox="0 0 16 16" fill="currentColor" width="12" height="12"><path d="M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z"/></svg>
|
|
1226
|
+
</button>
|
|
1227
|
+
</div>
|
|
1228
|
+
`;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1166
1231
|
return `
|
|
1167
1232
|
<div class="finding-item-wrapper">
|
|
1168
1233
|
<button class="finding-item finding-comment ${comment.parent_id ? 'comment-ai-origin' : 'comment-user-origin'}${isFileLevel ? ' file-level' : ''}${dismissedClass}" data-index="${index}" data-id="${comment.id || ''}" data-file="${comment.file || ''}" data-line="${lineNum || ''}" data-is-file-level="${isFileLevel ? '1' : '0'}" data-item-type="comment" title="${fullLocation}">
|
|
@@ -1173,6 +1238,7 @@ class AIPanel {
|
|
|
1173
1238
|
</div>
|
|
1174
1239
|
</button>
|
|
1175
1240
|
${actionButton}
|
|
1241
|
+
${chatAction}
|
|
1176
1242
|
</div>
|
|
1177
1243
|
`;
|
|
1178
1244
|
}
|
|
@@ -949,8 +949,8 @@ class AnalysisConfigModal {
|
|
|
949
949
|
tabBar.className = 'analysis-tab-bar';
|
|
950
950
|
tabBar.innerHTML = `
|
|
951
951
|
<button class="analysis-tab active" data-tab="single">Single Model</button>
|
|
952
|
-
<button class="analysis-tab" data-tab="council">Council
|
|
953
|
-
<button class="analysis-tab" data-tab="advanced">Advanced
|
|
952
|
+
<button class="analysis-tab" data-tab="council">Council</button>
|
|
953
|
+
<button class="analysis-tab" data-tab="advanced">Advanced</button>
|
|
954
954
|
`;
|
|
955
955
|
|
|
956
956
|
// Assemble
|