@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.
Files changed (62) hide show
  1. package/README.md +77 -4
  2. package/package.json +1 -1
  3. package/plugin/.claude-plugin/plugin.json +1 -1
  4. package/plugin/skills/review-requests/SKILL.md +4 -1
  5. package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
  6. package/plugin-code-critic/skills/analyze/SKILL.md +4 -3
  7. package/public/css/pr.css +1930 -114
  8. package/public/js/CONVENTIONS.md +16 -0
  9. package/public/js/components/AIPanel.js +66 -0
  10. package/public/js/components/AnalysisConfigModal.js +2 -2
  11. package/public/js/components/ChatPanel.js +2952 -0
  12. package/public/js/components/CouncilProgressModal.js +12 -16
  13. package/public/js/components/KeyboardShortcuts.js +3 -0
  14. package/public/js/components/PanelGroup.js +723 -0
  15. package/public/js/components/PreviewModal.js +3 -8
  16. package/public/js/index.js +8 -0
  17. package/public/js/local.js +17 -615
  18. package/public/js/modules/analysis-history.js +19 -68
  19. package/public/js/modules/comment-manager.js +57 -19
  20. package/public/js/modules/diff-context.js +176 -0
  21. package/public/js/modules/diff-renderer.js +30 -0
  22. package/public/js/modules/file-comment-manager.js +126 -105
  23. package/public/js/modules/file-list-merger.js +64 -0
  24. package/public/js/modules/panel-resizer.js +25 -6
  25. package/public/js/modules/suggestion-manager.js +40 -125
  26. package/public/js/pr.js +964 -159
  27. package/public/js/repo-settings.js +36 -6
  28. package/public/js/utils/category-emoji.js +44 -0
  29. package/public/js/utils/time.js +32 -0
  30. package/public/local.html +107 -70
  31. package/public/pr.html +107 -70
  32. package/public/repo-settings.html +32 -0
  33. package/src/ai/analyzer.js +5 -1
  34. package/src/ai/copilot-provider.js +39 -9
  35. package/src/ai/cursor-agent-provider.js +36 -7
  36. package/src/ai/gemini-provider.js +17 -4
  37. package/src/ai/prompts/config.js +7 -1
  38. package/src/ai/provider-availability.js +1 -1
  39. package/src/ai/provider.js +25 -37
  40. package/src/chat/CONVENTIONS.md +18 -0
  41. package/src/chat/pi-bridge.js +491 -0
  42. package/src/chat/prompt-builder.js +262 -0
  43. package/src/chat/session-manager.js +619 -0
  44. package/src/config.js +14 -0
  45. package/src/database.js +322 -15
  46. package/src/main.js +4 -17
  47. package/src/routes/analyses.js +721 -0
  48. package/src/routes/chat.js +655 -0
  49. package/src/routes/config.js +29 -8
  50. package/src/routes/context-files.js +223 -0
  51. package/src/routes/local.js +225 -1133
  52. package/src/routes/mcp.js +39 -30
  53. package/src/routes/pr.js +410 -52
  54. package/src/routes/reviews.js +1035 -0
  55. package/src/routes/shared.js +4 -29
  56. package/src/server.js +34 -12
  57. package/src/sse/review-events.js +46 -0
  58. package/src/utils/auto-context.js +88 -0
  59. package/src/utils/category-emoji.js +33 -0
  60. package/src/utils/diff-file-list.js +57 -0
  61. package/src/routes/analysis.js +0 -1600
  62. 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 <span class="beta-badge">BETA</span></button>
953
- <button class="analysis-tab" data-tab="advanced">Advanced <span class="beta-badge">BETA</span></button>
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