@in-the-loop-labs/pair-review 2.3.0 → 2.3.1
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/js/components/ChatPanel.js +4 -1
- package/public/js/components/PanelGroup.js +4 -2
- package/public/js/modules/panel-resizer.js +84 -1
- package/public/js/pr.js +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pair-review",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
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.
|
|
3
|
+
"version": "2.3.1",
|
|
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",
|
|
@@ -28,7 +28,7 @@ class ChatPanel {
|
|
|
28
28
|
this._contextItemId = null; // suggestion ID or comment ID from context
|
|
29
29
|
this._contextLineMeta = null; // { file, line_start, line_end } — set when opened with line context
|
|
30
30
|
this._pendingActionContext = null; // { type, itemId } — set by action button handlers, consumed by sendMessage
|
|
31
|
-
this._resizeConfig =
|
|
31
|
+
this._resizeConfig = ChatPanel.RESIZE_CONFIG;
|
|
32
32
|
this._analysisContextRemoved = false;
|
|
33
33
|
this._sessionAnalysisRunId = null; // tracks which AI run ID's context is loaded in the current session
|
|
34
34
|
this._openPromise = null; // concurrency guard for open()
|
|
@@ -3007,6 +3007,9 @@ class ChatPanel {
|
|
|
3007
3007
|
}
|
|
3008
3008
|
}
|
|
3009
3009
|
|
|
3010
|
+
/** Resize configuration for the chat panel, exposed as a static for cross-module use. */
|
|
3011
|
+
ChatPanel.RESIZE_CONFIG = { min: 300, default: 400, cssVar: '--chat-panel-width', storageKey: 'chat-panel-width' };
|
|
3012
|
+
|
|
3010
3013
|
// Make ChatPanel available globally
|
|
3011
3014
|
window.ChatPanel = ChatPanel;
|
|
3012
3015
|
|
|
@@ -60,11 +60,13 @@ class PanelGroup {
|
|
|
60
60
|
window.chatPanel = this.chatPanel;
|
|
61
61
|
|
|
62
62
|
// Create a full-height group resize handle for vertical layouts.
|
|
63
|
-
// Uses data-panel="
|
|
63
|
+
// Uses data-panel="panel-group" so PanelResizer updates both --ai-panel-width
|
|
64
|
+
// and --chat-panel-width in tandem (needed because the group CSS uses
|
|
65
|
+
// `width: max(--ai-panel-width, --chat-panel-width)`).
|
|
64
66
|
if (this.groupEl) {
|
|
65
67
|
this._groupResizeHandle = document.createElement('div');
|
|
66
68
|
this._groupResizeHandle.className = 'panel-group-resize-handle resize-handle resize-handle-left';
|
|
67
|
-
this._groupResizeHandle.dataset.panel = '
|
|
69
|
+
this._groupResizeHandle.dataset.panel = 'panel-group';
|
|
68
70
|
this.groupEl.insertBefore(this._groupResizeHandle, this.groupEl.firstChild);
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -26,6 +26,30 @@ window.PanelResizer = (function() {
|
|
|
26
26
|
// Note: chat-panel resize is handled by ChatPanel itself (see ChatPanel._bindResizeEvents)
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
// panel-group is a virtual panel used by the vertical-layout group resize handle.
|
|
30
|
+
// In vertical mode the group width is `max(--ai-panel-width, --chat-panel-width)`,
|
|
31
|
+
// so dragging the group handle must update BOTH CSS vars in tandem.
|
|
32
|
+
//
|
|
33
|
+
// Built lazily because ChatPanel.RESIZE_CONFIG is defined after this IIFE runs.
|
|
34
|
+
let _panelGroupConfig = null;
|
|
35
|
+
function getPanelGroupConfig() {
|
|
36
|
+
if (!_panelGroupConfig) {
|
|
37
|
+
const aiCfg = CONFIG['ai-panel'];
|
|
38
|
+
// ChatPanel.RESIZE_CONFIG is the canonical source for chat-panel sizing.
|
|
39
|
+
const chatCfg = window.ChatPanel?.RESIZE_CONFIG
|
|
40
|
+
?? { cssVar: '--chat-panel-width', storageKey: 'chat-panel-width', default: 400, min: 300 };
|
|
41
|
+
const panels = [
|
|
42
|
+
{ cssVar: aiCfg.cssVar, storageKey: aiCfg.storageKey, default: aiCfg.default },
|
|
43
|
+
{ cssVar: chatCfg.cssVar, storageKey: chatCfg.storageKey, default: chatCfg.default }
|
|
44
|
+
];
|
|
45
|
+
_panelGroupConfig = {
|
|
46
|
+
min: Math.max(aiCfg.min, chatCfg.min),
|
|
47
|
+
panels
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return _panelGroupConfig;
|
|
51
|
+
}
|
|
52
|
+
|
|
29
53
|
/**
|
|
30
54
|
* Compute the effective max width for a panel.
|
|
31
55
|
* For panels with a static max, returns that value.
|
|
@@ -39,6 +63,39 @@ window.PanelResizer = (function() {
|
|
|
39
63
|
return window.innerWidth - sidebarWidth - 100;
|
|
40
64
|
}
|
|
41
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Get the current panel-group width (the max of its sub-panel CSS vars).
|
|
68
|
+
* This matches the CSS `max()` expression used in vertical layouts.
|
|
69
|
+
* @returns {number} Current group width in pixels
|
|
70
|
+
*/
|
|
71
|
+
function getPanelGroupWidth() {
|
|
72
|
+
let maxWidth = 0;
|
|
73
|
+
for (const p of getPanelGroupConfig().panels) {
|
|
74
|
+
const val = parseInt(
|
|
75
|
+
getComputedStyle(document.documentElement).getPropertyValue(p.cssVar), 10
|
|
76
|
+
) || p.default;
|
|
77
|
+
if (val > maxWidth) maxWidth = val;
|
|
78
|
+
}
|
|
79
|
+
return maxWidth;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Set the panel-group width by updating ALL sub-panel CSS vars in tandem.
|
|
84
|
+
* @param {number} width - Desired width in pixels
|
|
85
|
+
* @param {boolean} save - Whether to persist to localStorage
|
|
86
|
+
*/
|
|
87
|
+
function setPanelGroupWidth(width, save = true) {
|
|
88
|
+
const effectiveMax = getEffectiveMax('ai-panel'); // same viewport constraint
|
|
89
|
+
const clamped = Math.max(getPanelGroupConfig().min, Math.min(effectiveMax, width));
|
|
90
|
+
|
|
91
|
+
for (const p of getPanelGroupConfig().panels) {
|
|
92
|
+
document.documentElement.style.setProperty(p.cssVar, `${clamped}px`);
|
|
93
|
+
if (save) {
|
|
94
|
+
localStorage.setItem(p.storageKey, clamped.toString());
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
42
99
|
// State
|
|
43
100
|
let isDragging = false;
|
|
44
101
|
let currentPanel = null;
|
|
@@ -127,6 +184,20 @@ window.PanelResizer = (function() {
|
|
|
127
184
|
if (!handle) return;
|
|
128
185
|
|
|
129
186
|
const panelName = handle.dataset.panel;
|
|
187
|
+
|
|
188
|
+
// panel-group: virtual panel — start width is the max of the sub-panels
|
|
189
|
+
if (panelName === 'panel-group') {
|
|
190
|
+
isDragging = true;
|
|
191
|
+
currentPanel = panelName;
|
|
192
|
+
startX = e.clientX;
|
|
193
|
+
startWidth = getPanelGroupWidth();
|
|
194
|
+
|
|
195
|
+
handle.classList.add('dragging');
|
|
196
|
+
document.body.classList.add('resizing');
|
|
197
|
+
e.preventDefault();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
130
201
|
const panelEl = panelName === 'sidebar'
|
|
131
202
|
? document.getElementById('files-sidebar')
|
|
132
203
|
: panelName === 'chat-panel'
|
|
@@ -157,6 +228,14 @@ window.PanelResizer = (function() {
|
|
|
157
228
|
function onMouseMove(e) {
|
|
158
229
|
if (!isDragging || !currentPanel) return;
|
|
159
230
|
|
|
231
|
+
// panel-group: update both sub-panel CSS vars in tandem
|
|
232
|
+
if (currentPanel === 'panel-group') {
|
|
233
|
+
const delta = startX - e.clientX; // right-side panel: left = wider
|
|
234
|
+
const newWidth = startWidth + delta;
|
|
235
|
+
setPanelGroupWidth(newWidth, false);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
160
239
|
const config = CONFIG[currentPanel];
|
|
161
240
|
if (!config) return;
|
|
162
241
|
|
|
@@ -182,7 +261,11 @@ window.PanelResizer = (function() {
|
|
|
182
261
|
if (!isDragging) return;
|
|
183
262
|
|
|
184
263
|
// Save final width
|
|
185
|
-
if (currentPanel) {
|
|
264
|
+
if (currentPanel === 'panel-group') {
|
|
265
|
+
// Persist both sub-panel widths
|
|
266
|
+
const finalWidth = getPanelGroupWidth();
|
|
267
|
+
setPanelGroupWidth(finalWidth, true);
|
|
268
|
+
} else if (currentPanel) {
|
|
186
269
|
const finalWidth = getPanelWidth(currentPanel);
|
|
187
270
|
const config = CONFIG[currentPanel];
|
|
188
271
|
if (config) {
|
package/public/js/pr.js
CHANGED
|
@@ -1400,6 +1400,15 @@ class PRManager {
|
|
|
1400
1400
|
}
|
|
1401
1401
|
} else {
|
|
1402
1402
|
this.viewedFiles.delete(filePath);
|
|
1403
|
+
// Auto-expand when unchecking viewed (match GitHub behavior)
|
|
1404
|
+
if (wrapper && wrapper.classList.contains('collapsed')) {
|
|
1405
|
+
wrapper.classList.remove('collapsed');
|
|
1406
|
+
this.collapsedFiles.delete(filePath);
|
|
1407
|
+
const header = wrapper.querySelector('.d2h-file-header');
|
|
1408
|
+
if (header) {
|
|
1409
|
+
window.DiffRenderer.updateFileHeaderState(header, true);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1403
1412
|
}
|
|
1404
1413
|
|
|
1405
1414
|
// Persist viewed state
|