@in-the-loop-labs/pair-review 2.3.0 → 2.3.2
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/AIPanel.js +51 -0
- package/public/js/components/ChatPanel.js +4 -1
- package/public/js/components/PanelGroup.js +42 -15
- package/public/js/local.js +3 -1
- package/public/js/modules/panel-resizer.js +84 -1
- package/public/js/pr.js +32 -2
- package/public/local.html +1 -1
- package/public/pr.html +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pair-review",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
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.2",
|
|
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",
|
|
@@ -207,6 +207,45 @@ class AIPanel {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Get the localStorage key for the collapsed state (per-review)
|
|
212
|
+
* @returns {string|null} Storage key or null if no PR context
|
|
213
|
+
*/
|
|
214
|
+
_getCollapsedStorageKey() {
|
|
215
|
+
if (!this.currentPRKey) return null;
|
|
216
|
+
return `pair-review-panel-collapsed_${this.currentPRKey}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Save the collapsed state to localStorage (per-review)
|
|
221
|
+
*/
|
|
222
|
+
_saveCollapsedState() {
|
|
223
|
+
const key = this._getCollapsedStorageKey();
|
|
224
|
+
if (key) {
|
|
225
|
+
localStorage.setItem(key, this.isCollapsed ? 'true' : 'false');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Restore the collapsed state from localStorage, or collapse for new reviews.
|
|
231
|
+
* If the user had the panel expanded for this review, expand it.
|
|
232
|
+
* Otherwise (new review or previously collapsed), collapse it.
|
|
233
|
+
*/
|
|
234
|
+
_restoreOrCollapsePanel() {
|
|
235
|
+
const key = this._getCollapsedStorageKey();
|
|
236
|
+
if (key) {
|
|
237
|
+
const stored = localStorage.getItem(key);
|
|
238
|
+
if (stored === 'false') {
|
|
239
|
+
this.expand();
|
|
240
|
+
} else {
|
|
241
|
+
// 'true' or no saved state (new review) → collapse
|
|
242
|
+
this.collapse();
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
this.collapse();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
210
249
|
/**
|
|
211
250
|
* Get the localStorage key for the filter state (per-review)
|
|
212
251
|
* @returns {string|null} Storage key or null if no PR context
|
|
@@ -275,6 +314,7 @@ class AIPanel {
|
|
|
275
314
|
// Set CSS variable to 0 so width calculations don't reserve space
|
|
276
315
|
document.documentElement.style.setProperty('--ai-panel-width', '0px');
|
|
277
316
|
window.panelGroup?._onReviewVisibilityChanged(false);
|
|
317
|
+
this._saveCollapsedState();
|
|
278
318
|
}
|
|
279
319
|
|
|
280
320
|
expand() {
|
|
@@ -285,6 +325,7 @@ class AIPanel {
|
|
|
285
325
|
// Restore CSS variable from saved width or default
|
|
286
326
|
document.documentElement.style.setProperty('--ai-panel-width', `${this.getEffectivePanelWidth()}px`);
|
|
287
327
|
window.panelGroup?._onReviewVisibilityChanged(true);
|
|
328
|
+
this._saveCollapsedState();
|
|
288
329
|
}
|
|
289
330
|
|
|
290
331
|
/**
|
|
@@ -296,6 +337,7 @@ class AIPanel {
|
|
|
296
337
|
*/
|
|
297
338
|
setPR(owner, repo, number) {
|
|
298
339
|
this.currentPRKey = `${owner}/${repo}#${number}`;
|
|
340
|
+
this._restoreOrCollapsePanel();
|
|
299
341
|
this.restoreSegmentSelection();
|
|
300
342
|
this.restoreFilterState();
|
|
301
343
|
}
|
|
@@ -327,6 +369,10 @@ class AIPanel {
|
|
|
327
369
|
*/
|
|
328
370
|
setAnalysisState(state) {
|
|
329
371
|
this.analysisState = state;
|
|
372
|
+
// Auto-expand panel when analysis starts
|
|
373
|
+
if (state === 'loading' && this.isCollapsed) {
|
|
374
|
+
this.expand();
|
|
375
|
+
}
|
|
330
376
|
// Re-render if currently showing empty state
|
|
331
377
|
if (this.findings.length === 0 && this.selectedSegment === 'ai') {
|
|
332
378
|
this.renderFindings();
|
|
@@ -1737,3 +1783,8 @@ class AIPanel {
|
|
|
1737
1783
|
document.addEventListener('DOMContentLoaded', () => {
|
|
1738
1784
|
window.aiPanel = new AIPanel();
|
|
1739
1785
|
});
|
|
1786
|
+
|
|
1787
|
+
// Export for testing
|
|
1788
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
1789
|
+
module.exports = { AIPanel };
|
|
1790
|
+
}
|
|
@@ -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
|
|
|
@@ -41,6 +41,7 @@ class PanelGroup {
|
|
|
41
41
|
this._reviewVisible = !document.getElementById('ai-panel')?.classList.contains('collapsed');
|
|
42
42
|
this._chatVisible = false;
|
|
43
43
|
this._popoverVisible = false;
|
|
44
|
+
this._currentPRKey = null; // Set via setPR() for per-review chat persistence
|
|
44
45
|
|
|
45
46
|
// Read persisted layout
|
|
46
47
|
const savedLayout = localStorage.getItem(PanelGroup.STORAGE_KEY);
|
|
@@ -60,11 +61,13 @@ class PanelGroup {
|
|
|
60
61
|
window.chatPanel = this.chatPanel;
|
|
61
62
|
|
|
62
63
|
// Create a full-height group resize handle for vertical layouts.
|
|
63
|
-
// Uses data-panel="
|
|
64
|
+
// Uses data-panel="panel-group" so PanelResizer updates both --ai-panel-width
|
|
65
|
+
// and --chat-panel-width in tandem (needed because the group CSS uses
|
|
66
|
+
// `width: max(--ai-panel-width, --chat-panel-width)`).
|
|
64
67
|
if (this.groupEl) {
|
|
65
68
|
this._groupResizeHandle = document.createElement('div');
|
|
66
69
|
this._groupResizeHandle.className = 'panel-group-resize-handle resize-handle resize-handle-left';
|
|
67
|
-
this._groupResizeHandle.dataset.panel = '
|
|
70
|
+
this._groupResizeHandle.dataset.panel = 'panel-group';
|
|
68
71
|
this.groupEl.insertBefore(this._groupResizeHandle, this.groupEl.firstChild);
|
|
69
72
|
}
|
|
70
73
|
|
|
@@ -77,20 +80,17 @@ class PanelGroup {
|
|
|
77
80
|
// Apply initial layout
|
|
78
81
|
this._applyLayout(this._layout);
|
|
79
82
|
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
if (chatState === 'available') {
|
|
83
|
-
this._restoreChatFromStorage();
|
|
84
|
-
} else {
|
|
85
|
-
// Chat not available yet — zero out CSS variable so max-width calcs are correct.
|
|
86
|
-
document.documentElement.style.setProperty('--chat-panel-width', '0px');
|
|
87
|
-
}
|
|
83
|
+
// Chat starts hidden; per-review state restored when setPR() is called.
|
|
84
|
+
document.documentElement.style.setProperty('--chat-panel-width', '0px');
|
|
88
85
|
|
|
89
86
|
// Listen for late chat-state transitions (config fetch may complete after constructor)
|
|
90
87
|
window.addEventListener('chat-state-changed', (e) => {
|
|
91
88
|
const state = e.detail?.state;
|
|
92
89
|
if (state === 'available') {
|
|
93
|
-
|
|
90
|
+
// Only restore if PR context is already set (setPR was called before chat became available)
|
|
91
|
+
if (this._currentPRKey) {
|
|
92
|
+
this._restoreChatFromStorage();
|
|
93
|
+
}
|
|
94
94
|
} else if (state === 'unavailable' && this.chatToggleBtn) {
|
|
95
95
|
this.chatToggleBtn.title = 'Install and configure Pi to enable chat';
|
|
96
96
|
}
|
|
@@ -420,10 +420,34 @@ class PanelGroup {
|
|
|
420
420
|
}
|
|
421
421
|
|
|
422
422
|
/**
|
|
423
|
-
*
|
|
423
|
+
* Set the current PR for per-review chat visibility persistence.
|
|
424
|
+
* Call this when a PR/review loads (after aiPanel.setPR).
|
|
425
|
+
* @param {string} prKey - e.g. "owner/repo#123" or "local/local#2"
|
|
426
|
+
*/
|
|
427
|
+
setPR(prKey) {
|
|
428
|
+
this._currentPRKey = prKey;
|
|
429
|
+
// Now that we know the review context, restore chat state if chat is available
|
|
430
|
+
if (this._isChatAvailable()) {
|
|
431
|
+
this._restoreChatFromStorage();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Get the per-review localStorage key for chat visibility.
|
|
437
|
+
* @returns {string|null} Storage key or null if no PR context
|
|
438
|
+
*/
|
|
439
|
+
_getChatVisibleStorageKey() {
|
|
440
|
+
if (!this._currentPRKey) return null;
|
|
441
|
+
return `${PanelGroup.CHAT_VISIBLE_KEY}_${this._currentPRKey}`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Restore chat visibility from localStorage (per-review).
|
|
446
|
+
* For new reviews (no saved state), chat stays hidden.
|
|
424
447
|
*/
|
|
425
448
|
_restoreChatFromStorage() {
|
|
426
|
-
const
|
|
449
|
+
const key = this._getChatVisibleStorageKey();
|
|
450
|
+
const savedChatVisible = key ? localStorage.getItem(key) : null;
|
|
427
451
|
if (savedChatVisible === 'true') {
|
|
428
452
|
this._chatVisible = true;
|
|
429
453
|
this.chatPanel.open({ suppressFocus: true });
|
|
@@ -476,8 +500,11 @@ class PanelGroup {
|
|
|
476
500
|
this.chatToggleBtn.classList.toggle('active', visible);
|
|
477
501
|
}
|
|
478
502
|
|
|
479
|
-
// Persist chat visibility
|
|
480
|
-
|
|
503
|
+
// Persist chat visibility (per-review)
|
|
504
|
+
const chatKey = this._getChatVisibleStorageKey();
|
|
505
|
+
if (chatKey) {
|
|
506
|
+
localStorage.setItem(chatKey, visible ? 'true' : 'false');
|
|
507
|
+
}
|
|
481
508
|
|
|
482
509
|
// Clear inline flex heights so the remaining panel fills the space
|
|
483
510
|
if (this._layout.startsWith('v-')) {
|
package/public/js/local.js
CHANGED
|
@@ -374,6 +374,7 @@ class LocalManager {
|
|
|
374
374
|
if (data.running && data.analysisId) {
|
|
375
375
|
manager.currentAnalysisId = data.analysisId;
|
|
376
376
|
manager.isAnalyzing = true;
|
|
377
|
+
window.aiPanel?.setAnalysisState('loading');
|
|
377
378
|
manager.setButtonAnalyzing(data.analysisId);
|
|
378
379
|
|
|
379
380
|
// Show the appropriate progress modal
|
|
@@ -791,10 +792,11 @@ class LocalManager {
|
|
|
791
792
|
window.aiPanel = new window.AIPanel();
|
|
792
793
|
}
|
|
793
794
|
|
|
794
|
-
// Set local context for AI Panel (restores
|
|
795
|
+
// Set local context for AI Panel and Panel Group (restores per-review state from localStorage)
|
|
795
796
|
if (window.aiPanel?.setPR) {
|
|
796
797
|
window.aiPanel.setPR('local', reviewData.repository, this.reviewId);
|
|
797
798
|
}
|
|
799
|
+
window.panelGroup?.setPR(`local/${reviewData.repository}#${this.reviewId}`);
|
|
798
800
|
|
|
799
801
|
// Load saved comments using the restored filter state from AI Panel
|
|
800
802
|
const includeDismissed = window.aiPanel?.showDismissedComments || false;
|
|
@@ -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
|
@@ -368,6 +368,9 @@ class PRManager {
|
|
|
368
368
|
|
|
369
369
|
/**
|
|
370
370
|
* Auto-trigger analysis if ?analyze=true is present in the URL.
|
|
371
|
+
* Skips refresh if data was just loaded fresh by loadPR (to avoid redundant fetches).
|
|
372
|
+
* Otherwise, refreshes PR data first to ensure we analyze the latest code.
|
|
373
|
+
* If refresh fails, proceeds with existing data rather than failing entirely.
|
|
371
374
|
* Cleans up the query parameter afterwards regardless of success or failure.
|
|
372
375
|
* @param {string} owner - Repository owner
|
|
373
376
|
* @param {string} repo - Repository name
|
|
@@ -378,6 +381,21 @@ class PRManager {
|
|
|
378
381
|
if (autoAnalyze === 'true' && !this.isAnalyzing) {
|
|
379
382
|
this._autoAnalyzeRequested = true;
|
|
380
383
|
try {
|
|
384
|
+
// Skip refresh if we just loaded fresh data (loadPR sets _justLoaded = true).
|
|
385
|
+
// Otherwise, refresh to ensure we have the latest PR data in case the worktree
|
|
386
|
+
// already existed but the PR has new commits since last load.
|
|
387
|
+
if (this._justLoaded) {
|
|
388
|
+
this._justLoaded = false;
|
|
389
|
+
} else {
|
|
390
|
+
try {
|
|
391
|
+
await this.refreshPR();
|
|
392
|
+
} catch (e) {
|
|
393
|
+
// If refresh fails, proceed with existing data - this is intentional.
|
|
394
|
+
// We'd rather analyze stale data than fail entirely.
|
|
395
|
+
console.warn('Pre-analysis refresh failed, proceeding with existing data', e);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
381
399
|
await this.startAnalysis(owner, repo, prNumber, null, {});
|
|
382
400
|
} finally {
|
|
383
401
|
this._autoAnalyzeRequested = false;
|
|
@@ -426,11 +444,12 @@ class PRManager {
|
|
|
426
444
|
window.aiPanel = new window.AIPanel();
|
|
427
445
|
}
|
|
428
446
|
|
|
429
|
-
// Set PR context for AI Panel (for
|
|
430
|
-
// This restores the filter state from localStorage
|
|
447
|
+
// Set PR context for AI Panel and Panel Group (for per-review localStorage keys)
|
|
448
|
+
// This restores the filter state and chat visibility from localStorage
|
|
431
449
|
if (window.aiPanel?.setPR) {
|
|
432
450
|
window.aiPanel.setPR(owner, repo, number);
|
|
433
451
|
}
|
|
452
|
+
window.panelGroup?.setPR(`${owner}/${repo}#${number}`);
|
|
434
453
|
|
|
435
454
|
// Load saved comments using the restored filter state from AI Panel
|
|
436
455
|
// If AI Panel has showDismissedComments=true (restored from localStorage), use that
|
|
@@ -470,6 +489,8 @@ class PRManager {
|
|
|
470
489
|
this.showError(error.message);
|
|
471
490
|
} finally {
|
|
472
491
|
this.setLoading(false);
|
|
492
|
+
// Mark that we just loaded fresh data - used by _maybeAutoAnalyze to skip redundant refresh
|
|
493
|
+
this._justLoaded = true;
|
|
473
494
|
}
|
|
474
495
|
}
|
|
475
496
|
|
|
@@ -1400,6 +1421,15 @@ class PRManager {
|
|
|
1400
1421
|
}
|
|
1401
1422
|
} else {
|
|
1402
1423
|
this.viewedFiles.delete(filePath);
|
|
1424
|
+
// Auto-expand when unchecking viewed (match GitHub behavior)
|
|
1425
|
+
if (wrapper && wrapper.classList.contains('collapsed')) {
|
|
1426
|
+
wrapper.classList.remove('collapsed');
|
|
1427
|
+
this.collapsedFiles.delete(filePath);
|
|
1428
|
+
const header = wrapper.querySelector('.d2h-file-header');
|
|
1429
|
+
if (header) {
|
|
1430
|
+
window.DiffRenderer.updateFileHeaderState(header, true);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1403
1433
|
}
|
|
1404
1434
|
|
|
1405
1435
|
// Persist viewed state
|
package/public/local.html
CHANGED
|
@@ -404,7 +404,7 @@
|
|
|
404
404
|
<!-- Right Panel Group (AI Panel + Chat) -->
|
|
405
405
|
<div class="right-panel-group" id="right-panel-group">
|
|
406
406
|
<!-- AI Analysis Panel (Right) -->
|
|
407
|
-
<aside class="ai-panel" id="ai-panel">
|
|
407
|
+
<aside class="ai-panel collapsed" id="ai-panel">
|
|
408
408
|
<div class="resize-handle resize-handle-left" data-panel="ai-panel"></div>
|
|
409
409
|
<div class="ai-panel-header">
|
|
410
410
|
<div class="ai-panel-title">
|
package/public/pr.html
CHANGED
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
<!-- Right Panel Group (AI Panel + Chat) -->
|
|
228
228
|
<div class="right-panel-group" id="right-panel-group">
|
|
229
229
|
<!-- AI Analysis Panel (Right) -->
|
|
230
|
-
<aside class="ai-panel" id="ai-panel">
|
|
230
|
+
<aside class="ai-panel collapsed" id="ai-panel">
|
|
231
231
|
<div class="resize-handle resize-handle-left" data-panel="ai-panel"></div>
|
|
232
232
|
<div class="ai-panel-header">
|
|
233
233
|
<div class="ai-panel-title">
|