@in-the-loop-labs/pair-review 3.1.2 → 3.1.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": "3.1.2",
3
+ "version": "3.1.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": "3.1.2",
3
+ "version": "3.1.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": "3.1.2",
3
+ "version": "3.1.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
@@ -7468,6 +7468,90 @@ body.resizing * {
7468
7468
  display: table-row;
7469
7469
  }
7470
7470
 
7471
+ /* When minimize mode is active, hide file-level comment cards */
7472
+ .comments-minimized .file-comment-card {
7473
+ display: none;
7474
+ }
7475
+
7476
+ /* Per-file expansion override — clicking a file-header indicator reveals that file's cards */
7477
+ .comments-minimized .file-comments-zone.file-comments-expanded .file-comment-card {
7478
+ display: block;
7479
+ }
7480
+
7481
+ /* File-header comment indicator — matches header button sizing, only visible when minimized */
7482
+ .file-comment-indicator {
7483
+ display: none;
7484
+ }
7485
+
7486
+ .comments-minimized .file-comment-indicator {
7487
+ display: inline-flex;
7488
+ align-items: center;
7489
+ justify-content: center;
7490
+ gap: 3px;
7491
+ width: 28px;
7492
+ height: 28px;
7493
+ padding: 0;
7494
+ background: transparent;
7495
+ border: 1px solid transparent;
7496
+ border-radius: 6px;
7497
+ cursor: pointer;
7498
+ color: var(--color-text-secondary, #656d76);
7499
+ transition: background-color 0.15s, color 0.15s, border-color 0.15s;
7500
+ flex-shrink: 0;
7501
+ font-size: 11px;
7502
+ line-height: 1;
7503
+ }
7504
+
7505
+ .comments-minimized .file-comment-indicator:has(.indicator-user):hover,
7506
+ .comments-minimized .file-comment-indicator:has(.indicator-adopted):hover {
7507
+ background-color: rgba(130, 80, 223, 0.1);
7508
+ border-color: var(--comment-primary, #8250df);
7509
+ }
7510
+
7511
+ .comments-minimized .file-comment-indicator:has(.indicator-ai):hover {
7512
+ background-color: rgba(217, 119, 6, 0.1);
7513
+ border-color: var(--color-accent-ai, #d97706);
7514
+ }
7515
+
7516
+ .comments-minimized .file-comment-indicator:has(.indicator-user),
7517
+ .comments-minimized .file-comment-indicator:has(.indicator-adopted) {
7518
+ border-color: var(--comment-primary, #8250df);
7519
+ color: var(--comment-primary, #8250df);
7520
+ }
7521
+
7522
+ .comments-minimized .file-comment-indicator:has(.indicator-ai) {
7523
+ border-color: var(--color-accent-ai, #d97706);
7524
+ color: var(--color-accent-ai, #d97706);
7525
+ }
7526
+
7527
+ .comments-minimized .file-comment-indicator.expanded {
7528
+ border-width: 2px;
7529
+ }
7530
+
7531
+ [data-theme="dark"] .comments-minimized .file-comment-indicator:has(.indicator-user),
7532
+ [data-theme="dark"] .comments-minimized .file-comment-indicator:has(.indicator-adopted) {
7533
+ border-color: var(--comment-primary, #a371f7);
7534
+ color: var(--comment-primary, #a371f7);
7535
+ }
7536
+
7537
+ [data-theme="dark"] .comments-minimized .file-comment-indicator:has(.indicator-ai) {
7538
+ border-color: var(--color-accent-ai, #fbbf24);
7539
+ color: var(--color-accent-ai, #fbbf24);
7540
+ }
7541
+
7542
+ [data-theme="dark"] .comments-minimized .file-comment-indicator:has(.indicator-user):hover,
7543
+ [data-theme="dark"] .comments-minimized .file-comment-indicator:has(.indicator-adopted):hover {
7544
+ background-color: rgba(163, 113, 247, 0.15);
7545
+ }
7546
+
7547
+ [data-theme="dark"] .comments-minimized .file-comment-indicator:has(.indicator-ai):hover {
7548
+ background-color: rgba(251, 191, 36, 0.15);
7549
+ }
7550
+
7551
+ [data-theme="dark"] .comments-minimized .file-comment-indicator.expanded {
7552
+ border-width: 2px;
7553
+ }
7554
+
7471
7555
  /* Indicator button on the right edge of diff code cells */
7472
7556
  .comment-indicator {
7473
7557
  position: absolute;
@@ -7530,11 +7614,11 @@ body.resizing * {
7530
7614
  }
7531
7615
 
7532
7616
  .comment-indicator .indicator-ai {
7533
- color: var(--ai-accent, #d97706);
7617
+ color: var(--color-accent-ai, #d97706);
7534
7618
  }
7535
7619
 
7536
7620
  .comment-indicator:has(.indicator-ai) {
7537
- border-color: var(--ai-accent, #d97706);
7621
+ border-color: var(--color-accent-ai, #d97706);
7538
7622
  background: rgba(217, 119, 6, 0.06);
7539
7623
  }
7540
7624
 
@@ -807,8 +807,9 @@ class AIPanel {
807
807
  const finding = this.findings.find(f => f.id === findingId);
808
808
  if (finding) {
809
809
  suggestionContext = {
810
+ suggestionId: findingId ? String(findingId) : null,
810
811
  title: finding.title || title,
811
- body: finding.body || '',
812
+ body: finding.formattedBody || finding.body || '',
812
813
  type: finding.type || '',
813
814
  file: finding.file || file,
814
815
  line_start: finding.line_start || null,
@@ -1002,6 +1003,8 @@ class AIPanel {
1002
1003
  if (targetSuggestion) {
1003
1004
  const minimizer = window.prManager?.commentMinimizer;
1004
1005
  if (minimizer?.active) {
1006
+ // Expand file-level comments so the target becomes visible
1007
+ minimizer.expandForElement(targetSuggestion);
1005
1008
  // Comments are minimized — scroll to the parent diff line instead
1006
1009
  const diffRow = minimizer.findDiffRowFor(targetSuggestion);
1007
1010
  (diffRow || targetSuggestion).scrollIntoView({ behavior: 'smooth', block: 'center' });
@@ -1069,9 +1072,9 @@ class AIPanel {
1069
1072
 
1070
1073
  if (targetElement) {
1071
1074
  const minimizer = window.prManager?.commentMinimizer;
1072
- if (minimizer?.active && !isFileLevel) {
1073
- // Comments are minimized — scroll to the parent diff line instead
1074
- const diffRow = minimizer.findDiffRowFor(targetElement);
1075
+ if (minimizer?.active) {
1076
+ minimizer.expandForElement(targetElement);
1077
+ const diffRow = isFileLevel ? null : minimizer.findDiffRowFor(targetElement);
1075
1078
  (diffRow || targetElement).scrollIntoView({ behavior: 'smooth', block: 'center' });
1076
1079
  } else {
1077
1080
  targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
@@ -32,6 +32,7 @@ class ChatPanel {
32
32
  this._pendingContext = [];
33
33
  this._pendingContextData = [];
34
34
  this._pendingDiffStateNotifications = [];
35
+ this._pendingUserActionHints = [];
35
36
  this._contextSource = null; // 'suggestion' or 'user' — set when opened with context
36
37
  this._contextItemId = null; // suggestion ID or comment ID from context
37
38
  this._contextLineMeta = null; // { file, line_start, line_end } — set when opened with line context
@@ -667,6 +668,7 @@ class ChatPanel {
667
668
  this._pendingContext = [];
668
669
  this._pendingContextData = [];
669
670
  this._pendingDiffStateNotifications = [];
671
+ this._pendingUserActionHints = [];
670
672
  this._contextSource = null;
671
673
  this._contextItemId = null;
672
674
  this._contextLineMeta = null;
@@ -1030,6 +1032,7 @@ class ChatPanel {
1030
1032
  this._pendingContext = [];
1031
1033
  this._pendingContextData = [];
1032
1034
  this._pendingDiffStateNotifications = [];
1035
+ this._pendingUserActionHints = [];
1033
1036
  this._contextSource = null;
1034
1037
  this._contextItemId = null;
1035
1038
  this._contextLineMeta = null;
@@ -1269,12 +1272,28 @@ class ChatPanel {
1269
1272
  this._pendingDiffStateNotifications = [];
1270
1273
  }
1271
1274
 
1275
+ // Snapshot user-action-hints queue for error recovery (invisible to user, no UI cards)
1276
+ const savedUserActionHints = this._pendingUserActionHints.slice();
1277
+ let userActionPrefix = '';
1278
+ if (this._pendingUserActionHints.length > 0) {
1279
+ userActionPrefix = '[User Action Hints]\n' + this._pendingUserActionHints.join('\n');
1280
+ this._pendingUserActionHints = [];
1281
+ }
1282
+
1283
+ // Combine invisible prefixes (diff state + user action hints)
1284
+ let invisiblePrefix = '';
1285
+ if (diffStatePrefix && userActionPrefix) {
1286
+ invisiblePrefix = diffStatePrefix + '\n\n' + userActionPrefix;
1287
+ } else {
1288
+ invisiblePrefix = diffStatePrefix || userActionPrefix;
1289
+ }
1290
+
1272
1291
  const savedContext = this._pendingContext;
1273
1292
  const savedContextData = this._pendingContextData;
1274
1293
  if (this._pendingContext.length > 0) {
1275
1294
  const userContext = this._pendingContext.join('\n\n');
1276
- payload.context = diffStatePrefix
1277
- ? diffStatePrefix + '\n\n' + userContext
1295
+ payload.context = invisiblePrefix
1296
+ ? invisiblePrefix + '\n\n' + userContext
1278
1297
  : userContext;
1279
1298
  payload.contextData = this._pendingContextData;
1280
1299
  this._pendingContext = [];
@@ -1287,8 +1306,8 @@ class ChatPanel {
1287
1306
  if (btn) btn.remove();
1288
1307
  delete card.dataset.contextIndex;
1289
1308
  });
1290
- } else if (diffStatePrefix) {
1291
- payload.context = diffStatePrefix;
1309
+ } else if (invisiblePrefix) {
1310
+ payload.context = invisiblePrefix;
1292
1311
  }
1293
1312
 
1294
1313
  // Lock analysis context card (not indexed, handled separately from pending context)
@@ -1353,6 +1372,7 @@ class ChatPanel {
1353
1372
  this._pendingContext = savedContext;
1354
1373
  this._pendingContextData = savedContextData;
1355
1374
  this._pendingDiffStateNotifications = [...savedDiffState, ...this._pendingDiffStateNotifications];
1375
+ this._pendingUserActionHints = [...savedUserActionHints, ...this._pendingUserActionHints];
1356
1376
  // Restore removability on context cards that were locked before the failed send
1357
1377
  this._restoreRemovableCards();
1358
1378
  console.error('[ChatPanel] Error sending message:', error);
@@ -1371,6 +1391,16 @@ class ChatPanel {
1371
1391
  this._pendingDiffStateNotifications.push(message);
1372
1392
  }
1373
1393
 
1394
+ /**
1395
+ * Queue an invisible user-action hint for the chat agent.
1396
+ * Like diff-state notifications, these do NOT render UI cards and survive panel close.
1397
+ * Drained into the context parameter on the next sendMessage() call.
1398
+ * @param {string} message - Description of the user action (e.g., "[User Action: adopted suggestion 42]")
1399
+ */
1400
+ queueUserActionHint(message) {
1401
+ this._pendingUserActionHints.push(message);
1402
+ }
1403
+
1374
1404
  /**
1375
1405
  * Store pending context and render a compact context card in the UI.
1376
1406
  * Called when the user clicks "Ask about this" on a suggestion.
@@ -1312,6 +1312,9 @@ class CouncilProgressModal {
1312
1312
  <span class="council-voice-icon pending">\u25CB</span>
1313
1313
  <span class="council-voice-label">Consolidation</span>
1314
1314
  <span class="council-voice-status pending">Pending</span>
1315
+ <div class="council-voice-detail">
1316
+ <div class="council-voice-snippet" style="display: none;"></div>
1317
+ </div>
1315
1318
  </div>
1316
1319
  `;
1317
1320
  }
@@ -57,11 +57,29 @@ class DiffOptionsDropdown {
57
57
  this._outsideClickHandler = null;
58
58
  this._escapeHandler = null;
59
59
 
60
- // Scope state
61
- const LS = window.LocalScope;
60
+ // Scope state — resolve LocalScope with inline fallback so the scope selector
61
+ // renders even if window.LocalScope failed to load (race condition guard).
62
+ const FALLBACK_STOPS = ['branch', 'staged', 'unstaged', 'untracked']; // Keep in sync with local-scope.js:STOPS
63
+ const FALLBACK_DEFAULT = { start: 'unstaged', end: 'untracked' };
64
+ this._localScope = window.LocalScope || {
65
+ STOPS: FALLBACK_STOPS,
66
+ DEFAULT_SCOPE: FALLBACK_DEFAULT,
67
+ isValidScope: (s, e) => {
68
+ const si = FALLBACK_STOPS.indexOf(s);
69
+ const ei = FALLBACK_STOPS.indexOf(e);
70
+ return si !== -1 && ei !== -1 && si <= ei;
71
+ },
72
+ scopeIncludes: (s, e, stop) => {
73
+ const si = FALLBACK_STOPS.indexOf(s);
74
+ const ei = FALLBACK_STOPS.indexOf(e);
75
+ const ti = FALLBACK_STOPS.indexOf(stop);
76
+ return ti !== -1 && ti >= si && ti <= ei;
77
+ }
78
+ };
79
+ const LS = this._localScope;
62
80
  this._branchAvailable = Boolean(branchAvailable);
63
- this._scopeStart = (initialScope && initialScope.start) || (LS ? LS.DEFAULT_SCOPE.start : 'unstaged');
64
- this._scopeEnd = (initialScope && initialScope.end) || (LS ? LS.DEFAULT_SCOPE.end : 'untracked');
81
+ this._scopeStart = (initialScope && initialScope.start) || LS.DEFAULT_SCOPE.start;
82
+ this._scopeEnd = (initialScope && initialScope.end) || LS.DEFAULT_SCOPE.end;
65
83
  this._scopeStops = [];
66
84
  this._scopeTrackEl = null;
67
85
  this._scopeDebounceTimer = null;
@@ -144,7 +162,7 @@ class DiffOptionsDropdown {
144
162
  /** Programmatically set scope. */
145
163
  set scope(val) {
146
164
  if (!val) return;
147
- const LS = window.LocalScope;
165
+ const LS = this._localScope;
148
166
  if (LS && !LS.isValidScope(val.start, val.end)) return;
149
167
  this._scopeStart = val.start;
150
168
  this._scopeEnd = val.end;
@@ -188,8 +206,11 @@ class DiffOptionsDropdown {
188
206
  popover.style.zIndex = '1100';
189
207
  popover.style.transition = 'opacity 0.15s ease, transform 0.15s ease';
190
208
 
191
- // Scope selector first — only in local mode
192
- if (window.PAIR_REVIEW_LOCAL_MODE && window.LocalScope) {
209
+ // Scope selector first — only in local mode.
210
+ // Belt-and-suspenders: also render when scope callbacks were explicitly provided,
211
+ // in case a race condition prevents the globals from being set in time.
212
+ const hasLocalScope = (window.PAIR_REVIEW_LOCAL_MODE && window.LocalScope) || this._onScopeChange;
213
+ if (hasLocalScope) {
193
214
  this._renderScopeSelector(popover);
194
215
 
195
216
  // Divider between scope selector and whitespace checkbox
@@ -261,7 +282,7 @@ class DiffOptionsDropdown {
261
282
  }
262
283
 
263
284
  _renderScopeSelector(popover) {
264
- const LS = window.LocalScope;
285
+ const LS = this._localScope;
265
286
 
266
287
  // Section container — generous horizontal padding so dots/labels breathe
267
288
  const section = document.createElement('div');
@@ -362,13 +383,43 @@ class DiffOptionsDropdown {
362
383
  stopEl.appendChild(dot);
363
384
  stopEl.appendChild(labelEl);
364
385
 
386
+ // Custom tooltip element (positioned above the dot, hidden by default)
387
+ const tooltipEl = document.createElement('div');
388
+ tooltipEl.style.position = 'absolute';
389
+ tooltipEl.style.bottom = '100%';
390
+ tooltipEl.style.left = '50%';
391
+ tooltipEl.style.transform = 'translateX(-50%)';
392
+ tooltipEl.style.marginBottom = '6px';
393
+ tooltipEl.style.padding = '4px 8px';
394
+ tooltipEl.style.fontSize = '11px';
395
+ tooltipEl.style.lineHeight = '1.3';
396
+ tooltipEl.style.color = 'var(--color-text-on-emphasis, #ffffff)';
397
+ tooltipEl.style.background = 'var(--color-neutral-emphasis, #24292f)';
398
+ tooltipEl.style.borderRadius = '4px';
399
+ tooltipEl.style.whiteSpace = 'nowrap';
400
+ tooltipEl.style.pointerEvents = 'none';
401
+ tooltipEl.style.opacity = '0';
402
+ tooltipEl.style.transition = 'opacity 0.12s ease';
403
+ tooltipEl.style.zIndex = '2';
404
+ stopEl.style.position = 'relative';
405
+ stopEl.appendChild(tooltipEl);
406
+
407
+ stopEl.addEventListener('mouseenter', () => {
408
+ if (tooltipEl.textContent) {
409
+ tooltipEl.style.opacity = '1';
410
+ }
411
+ });
412
+ stopEl.addEventListener('mouseleave', () => {
413
+ tooltipEl.style.opacity = '0';
414
+ });
415
+
365
416
  stopEl.addEventListener('click', (e) => {
366
417
  e.stopPropagation();
367
418
  this._handleStopClick(stop, e);
368
419
  });
369
420
 
370
421
  stopsRow.appendChild(stopEl);
371
- this._scopeStops.push({ stop, dotEl: dot, labelEl, containerEl: stopEl });
422
+ this._scopeStops.push({ stop, dotEl: dot, labelEl, containerEl: stopEl, tooltipEl });
372
423
  });
373
424
 
374
425
  trackContainer.appendChild(stopsRow);
@@ -382,7 +433,7 @@ class DiffOptionsDropdown {
382
433
  }
383
434
 
384
435
  _handleStopClick(clickedStop, event) {
385
- const LS = window.LocalScope;
436
+ const LS = this._localScope;
386
437
  if (!LS) return;
387
438
 
388
439
  // Branch disabled? Ignore.
@@ -396,10 +447,14 @@ class DiffOptionsDropdown {
396
447
  let newStart = this._scopeStart;
397
448
  let newEnd = this._scopeEnd;
398
449
 
399
- // Alt/Option-click: solo-select this single stop
450
+ // 'unstaged' is always included — the AI reads files from the working
451
+ // tree, so the diff must always cover at least the unstaged state.
452
+ const ui = stops.indexOf('unstaged');
453
+
454
+ // Alt/Option-click: select this stop with minimum scope including unstaged
400
455
  if (event && event.altKey) {
401
- newStart = clickedStop;
402
- newEnd = clickedStop;
456
+ newStart = stops[Math.min(ci, ui)];
457
+ newEnd = stops[Math.max(ci, ui)];
403
458
  } else {
404
459
  // Checkbox-like toggle with contiguity constraint
405
460
  const included = ci >= si && ci <= ei;
@@ -407,6 +462,7 @@ class DiffOptionsDropdown {
407
462
  if (included) {
408
463
  // Toggling OFF — only allowed at boundaries, and range must have >1 stop
409
464
  if (si === ei) return;
465
+ if (clickedStop === 'unstaged') return; // unstaged is mandatory
410
466
  if (ci === si) {
411
467
  newStart = stops[si + 1];
412
468
  } else if (ci === ei) {
@@ -457,30 +513,48 @@ class DiffOptionsDropdown {
457
513
  }
458
514
 
459
515
  _updateScopeUI() {
460
- const LS = window.LocalScope;
516
+ const LS = this._localScope;
461
517
  if (!LS || !this._scopeStops.length) return;
462
518
 
463
519
  const stops = LS.STOPS;
464
520
  const si = stops.indexOf(this._scopeStart);
465
521
  const ei = stops.indexOf(this._scopeEnd);
466
522
 
467
- this._scopeStops.forEach(({ stop, dotEl, labelEl, containerEl }, i) => {
523
+ this._scopeStops.forEach(({ stop, dotEl, labelEl, containerEl, tooltipEl }, i) => {
468
524
  const included = LS.scopeIncludes(this._scopeStart, this._scopeEnd, stop);
469
525
  const isBranch = stop === 'branch';
470
526
  const disabled = isBranch && !this._branchAvailable;
471
527
 
472
- // Determine if clicking this stop would do anything (for cursor hint)
473
- const isBoundary = included && (i === si || i === ei) && si !== ei;
528
+ // Determine if clicking this stop would do anything (for cursor hint).
529
+ // 'unstaged' is mandatory and cannot be toggled off, so it is never a
530
+ // clickable boundary even when it sits at a range edge.
531
+ const isMandatory = stop === 'unstaged';
532
+ const atRangeEdge = included && (i === si || i === ei) && si !== ei;
533
+ const isBoundary = atRangeEdge && !isMandatory;
474
534
  const isAdjacent = !included && (i === si - 1 || i === ei + 1);
475
535
  const clickable = !disabled && (isBoundary || isAdjacent);
476
536
 
537
+ // Tooltip for disabled branch stop
538
+ containerEl.title = disabled ? 'No feature branch detected' : '';
539
+
540
+ // Mandatory stop sitting at a range edge — user might expect to toggle
541
+ // it off but can't. Show not-allowed cursor and explanatory tooltip.
542
+ const mandatoryEdge = isMandatory && atRangeEdge;
543
+
544
+ // Update tooltip text (empty string hides the tooltip on hover)
545
+ if (tooltipEl) {
546
+ tooltipEl.textContent = mandatoryEdge
547
+ ? 'Unstaged changes are always included \u2014 the agent reads from your working tree'
548
+ : '';
549
+ }
550
+
477
551
  if (disabled) {
478
552
  // Disabled state
479
553
  dotEl.style.background = 'var(--color-bg-tertiary, #f6f8fa)';
480
554
  dotEl.style.borderColor = 'var(--color-border-secondary, #e1e4e8)';
481
555
  dotEl.style.boxShadow = 'none';
482
556
  labelEl.style.color = 'var(--color-text-tertiary, #8b949e)';
483
- containerEl.style.cursor = 'not-allowed';
557
+ containerEl.style.cursor = 'default';
484
558
  containerEl.style.opacity = '0.5';
485
559
  } else if (included) {
486
560
  // Included (filled) state
@@ -489,7 +563,7 @@ class DiffOptionsDropdown {
489
563
  dotEl.style.boxShadow = '0 0 0 2px rgba(139, 92, 246, 0.2)';
490
564
  labelEl.style.color = 'var(--color-text-primary, #24292f)';
491
565
  labelEl.style.fontWeight = '600';
492
- containerEl.style.cursor = clickable ? 'pointer' : 'default';
566
+ containerEl.style.cursor = clickable ? 'pointer' : (mandatoryEdge ? 'not-allowed' : 'default');
493
567
  containerEl.style.opacity = '1';
494
568
  } else {
495
569
  // Excluded (empty) state
@@ -371,6 +371,8 @@ class SuggestionNavigator {
371
371
  if (suggestionEl) {
372
372
  const minimizer = window.prManager?.commentMinimizer;
373
373
  if (minimizer?.active) {
374
+ // Expand file-level comments so the target becomes visible
375
+ minimizer.expandForElement(suggestionEl);
374
376
  // Comments are minimized — scroll to the parent diff line instead
375
377
  const diffRow = minimizer.findDiffRowFor(suggestionEl);
376
378
  if (diffRow) {
@@ -503,8 +503,15 @@ class CommentManager {
503
503
  // Refresh minimize-mode indicators so the new comment is reflected
504
504
  if (window.prManager?.commentMinimizer) {
505
505
  window.prManager.commentMinimizer.refreshIndicators();
506
+ // Auto-expand so the new comment stays visible in minimize mode
507
+ const newRow = document.querySelector(`.user-comment-row[data-comment-id="${commentData.id}"]`);
508
+ if (newRow) {
509
+ window.prManager.commentMinimizer.expandForElement(newRow);
510
+ }
506
511
  }
507
512
 
513
+ window.chatPanel?.queueUserActionHint(`[User Action: created comment ${result.commentId}]`);
514
+
508
515
  } catch (error) {
509
516
  console.error('Error saving comment:', error);
510
517
  alert('Failed to save comment');