@in-the-loop-labs/pair-review 2.4.3 → 2.4.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@in-the-loop-labs/pair-review",
3
- "version": "2.4.3",
3
+ "version": "2.4.4",
4
4
  "description": "Your AI-powered code review partner - Close the feedback loop with AI coding agents",
5
5
  "main": "src/server.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pair-review",
3
- "version": "2.4.3",
3
+ "version": "2.4.4",
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.4.3",
3
+ "version": "2.4.4",
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
@@ -1188,8 +1188,7 @@
1188
1188
  background: var(--color-bg-primary);
1189
1189
  border: 1px solid var(--color-border-primary);
1190
1190
  border-radius: 8px;
1191
- overflow-x: auto;
1192
- overflow-y: visible;
1191
+ overflow: visible; /* Must be visible so sticky file headers work relative to .diff-view */
1193
1192
  position: relative;
1194
1193
  min-width: 0;
1195
1194
  padding-bottom: 1px;
@@ -1335,7 +1334,7 @@
1335
1334
  /* Diff styling improvements */
1336
1335
  .d2h-file-wrapper[data-file-name] {
1337
1336
  margin-bottom: 24px;
1338
- overflow-x: auto; /* Allow horizontal scroll for long code lines */
1337
+ overflow-x: visible; /* Must be visible for sticky file headers to work */
1339
1338
  max-width: 100%;
1340
1339
  }
1341
1340
 
@@ -1348,6 +1347,9 @@
1348
1347
  border-bottom: 1px solid var(--color-border-primary);
1349
1348
  font-weight: 600;
1350
1349
  font-size: 14px;
1350
+ position: sticky;
1351
+ top: var(--toolbar-height, 0px);
1352
+ z-index: 4;
1351
1353
  }
1352
1354
 
1353
1355
  .d2h-file-name {
@@ -1540,7 +1542,8 @@
1540
1542
  color: #f85149;
1541
1543
  }
1542
1544
 
1543
- /* Hide diff table when collapsed */
1545
+ /* Hide diff content when collapsed */
1546
+ .d2h-file-wrapper.collapsed .d2h-file-body,
1544
1547
  .d2h-file-wrapper.collapsed .d2h-diff-table {
1545
1548
  display: none;
1546
1549
  }
@@ -1560,6 +1563,12 @@
1560
1563
  cursor: pointer;
1561
1564
  }
1562
1565
 
1566
+ /* Scrollable wrapper for diff tables — provides per-file horizontal scroll
1567
+ now that .d2h-file-wrapper and .diff-container use overflow:visible for sticky headers */
1568
+ .d2h-file-body {
1569
+ overflow-x: auto;
1570
+ }
1571
+
1563
1572
  .d2h-diff-table {
1564
1573
  width: 100%;
1565
1574
  border-collapse: collapse;
@@ -6288,6 +6297,9 @@ body:not([data-theme="dark"]) .theme-icon-light {
6288
6297
  background: var(--color-bg-primary);
6289
6298
  border-bottom: 1px solid var(--color-border-primary);
6290
6299
  flex-shrink: 0;
6300
+ position: sticky;
6301
+ top: 0;
6302
+ z-index: 5;
6291
6303
  }
6292
6304
 
6293
6305
  .diff-toolbar .sidebar-toggle-collapsed {
@@ -6759,11 +6771,10 @@ body:not([data-theme="dark"]) .theme-icon-light {
6759
6771
  }
6760
6772
 
6761
6773
  .main-layout .diff-container {
6762
- flex: 1;
6774
+ flex: 1 0 auto; /* Grow to fill, but don't shrink below content so .diff-view scrolls */
6763
6775
  min-width: 0; /* Prevent flex item from expanding beyond container */
6764
6776
  padding: 16px;
6765
- overflow-y: auto;
6766
- overflow-x: auto; /* Allow scroll as fallback for unbreakable content */
6777
+ overflow: visible; /* Must be visible so sticky file headers work relative to .diff-view */
6767
6778
  }
6768
6779
 
6769
6780
  /* --------------------------------------------------------------------------
@@ -402,11 +402,6 @@
402
402
  state.fetchedAt = data.fetched_at;
403
403
 
404
404
  renderCollectionTable(container, state, collection);
405
-
406
- // Auto-refresh on first load if cache is empty
407
- if (state.prs.length === 0 && !state.fetchedAt) {
408
- refreshCollectionPrs(collection, containerId, state);
409
- }
410
405
  } catch (error) {
411
406
  console.error('Error loading ' + collection + ':', error);
412
407
  container.innerHTML =
@@ -1164,11 +1159,11 @@
1164
1159
  if (collectionRow && !event.target.closest('a')) {
1165
1160
  var prUrl = collectionRow.dataset.prUrl;
1166
1161
  if (prUrl) {
1167
- // Switch to PR tab to show loading state
1162
+ // Switch to PR tab to show loading state (do NOT persist to
1163
+ // localStorage – the user's intentional tab choice should be preserved)
1168
1164
  var tabBar = document.getElementById('unified-tab-bar');
1169
1165
  var prTabBtn = tabBar.querySelector('[data-tab="pr-tab"]');
1170
1166
  switchTab(tabBar, prTabBtn);
1171
- localStorage.setItem(TAB_STORAGE_KEY, 'pr-tab');
1172
1167
 
1173
1168
  // Populate input and submit the form programmatically
1174
1169
  var input = document.getElementById('pr-url-input');
@@ -1204,19 +1199,25 @@
1204
1199
  const unifiedTabBtn = event.target.closest('#unified-tab-bar .tab-btn');
1205
1200
  if (unifiedTabBtn) {
1206
1201
  const tabBar = document.getElementById('unified-tab-bar');
1207
- switchTab(tabBar, unifiedTabBtn, function (tabId) {
1202
+ switchTab(tabBar, unifiedTabBtn, async function (tabId) {
1208
1203
  // Persist tab choice
1209
1204
  localStorage.setItem(TAB_STORAGE_KEY, tabId);
1210
1205
  // Lazy-load local reviews on first switch
1211
1206
  if (tabId === 'local-tab' && !localReviewsPagination.loaded) {
1212
1207
  loadLocalReviews();
1213
1208
  }
1214
- // Lazy-load GitHub collection tabs on first switch
1215
- if (tabId === 'review-requests-tab' && !reviewRequestsState.loaded) {
1216
- loadCollectionPrs('review-requests', 'review-requests-container', reviewRequestsState);
1209
+ // Load cached data on first switch, then always refresh from GitHub
1210
+ if (tabId === 'review-requests-tab') {
1211
+ if (!reviewRequestsState.loaded) {
1212
+ await loadCollectionPrs('review-requests', 'review-requests-container', reviewRequestsState);
1213
+ }
1214
+ refreshCollectionPrs('review-requests', 'review-requests-container', reviewRequestsState);
1217
1215
  }
1218
- if (tabId === 'my-prs-tab' && !myPrsState.loaded) {
1219
- loadCollectionPrs('my-prs', 'my-prs-container', myPrsState);
1216
+ if (tabId === 'my-prs-tab') {
1217
+ if (!myPrsState.loaded) {
1218
+ await loadCollectionPrs('my-prs', 'my-prs-container', myPrsState);
1219
+ }
1220
+ refreshCollectionPrs('my-prs', 'my-prs-container', myPrsState);
1220
1221
  }
1221
1222
  });
1222
1223
  return;
@@ -1248,12 +1249,14 @@
1248
1249
  loadLocalReviews();
1249
1250
  }
1250
1251
 
1251
- // If a GitHub collection tab is active, load it immediately
1252
+ // If a GitHub collection tab is active, load cached data then refresh from GitHub
1252
1253
  if (savedTab === 'review-requests-tab') {
1253
- loadCollectionPrs('review-requests', 'review-requests-container', reviewRequestsState);
1254
+ loadCollectionPrs('review-requests', 'review-requests-container', reviewRequestsState)
1255
+ .then(function () { refreshCollectionPrs('review-requests', 'review-requests-container', reviewRequestsState); });
1254
1256
  }
1255
1257
  if (savedTab === 'my-prs-tab') {
1256
- loadCollectionPrs('my-prs', 'my-prs-container', myPrsState);
1258
+ loadCollectionPrs('my-prs', 'my-prs-container', myPrsState)
1259
+ .then(function () { refreshCollectionPrs('my-prs', 'my-prs-container', myPrsState); });
1257
1260
  }
1258
1261
 
1259
1262
  // Set up start review form handler
package/public/js/pr.js CHANGED
@@ -180,6 +180,9 @@ class PRManager {
180
180
  this.initAnalysisConfigModal();
181
181
  this.initKeyboardShortcuts();
182
182
 
183
+ // Track toolbar height for sticky file headers (they sit below the sticky toolbar)
184
+ this._initToolbarHeightTracking();
185
+
183
186
  // Initialize diff options dropdown (gear icon for whitespace toggle).
184
187
  // Must happen before init() so the persisted hideWhitespace state is
185
188
  // applied before the first loadAndDisplayFiles() call.
@@ -238,6 +241,27 @@ class PRManager {
238
241
  };
239
242
  }
240
243
 
244
+ /**
245
+ * Keep --toolbar-height CSS variable in sync with the actual toolbar size
246
+ * so sticky file headers can position themselves below the sticky toolbar.
247
+ */
248
+ _initToolbarHeightTracking() {
249
+ const toolbar = document.querySelector('.diff-toolbar');
250
+ if (!toolbar) return;
251
+
252
+ const update = () => {
253
+ document.documentElement.style.setProperty(
254
+ '--toolbar-height', toolbar.offsetHeight + 'px'
255
+ );
256
+ };
257
+ update();
258
+
259
+ // Re-measure when toolbar resizes (e.g. analysis dots appear/disappear)
260
+ if (typeof ResizeObserver !== 'undefined') {
261
+ new ResizeObserver(update).observe(toolbar);
262
+ }
263
+ }
264
+
241
265
  /**
242
266
  * Set up event handlers
243
267
  */
@@ -1182,7 +1206,13 @@ class PRManager {
1182
1206
  }
1183
1207
 
1184
1208
  table.appendChild(tbody);
1185
- wrapper.appendChild(table);
1209
+
1210
+ // Wrap table in a scrollable container for horizontal scroll of long code lines
1211
+ // (parent elements use overflow:visible to support sticky file headers)
1212
+ const fileBody = document.createElement('div');
1213
+ fileBody.className = 'd2h-file-body';
1214
+ fileBody.appendChild(table);
1215
+ wrapper.appendChild(fileBody);
1186
1216
 
1187
1217
  return wrapper;
1188
1218
  }
@@ -4784,7 +4814,11 @@ class PRManager {
4784
4814
  table.className = 'd2h-diff-table';
4785
4815
  const tbody = this._buildContextChunkTbody(data, contextFile);
4786
4816
  table.appendChild(tbody);
4787
- wrapper.appendChild(table);
4817
+
4818
+ const fileBody = document.createElement('div');
4819
+ fileBody.className = 'd2h-file-body';
4820
+ fileBody.appendChild(table);
4821
+ wrapper.appendChild(fileBody);
4788
4822
 
4789
4823
  // Insert in sorted path order among existing file wrappers
4790
4824
  const allWrappers = [...diffContainer.querySelectorAll('.d2h-file-wrapper')];