@in-the-loop-labs/pair-review 1.6.2 → 2.0.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.
Files changed (63) 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 +1962 -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 +2955 -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 +103 -20
  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 +1009 -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 +45 -11
  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 +272 -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 +274 -0
  51. package/src/routes/local.js +225 -1133
  52. package/src/routes/mcp.js +39 -30
  53. package/src/routes/pr.js +424 -58
  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-annotator.js +75 -1
  61. package/src/utils/diff-file-list.js +57 -0
  62. package/src/routes/analysis.js +0 -1600
  63. package/src/routes/comments.js +0 -534
@@ -200,6 +200,16 @@ class RepoSettingsPage {
200
200
  });
201
201
  }
202
202
 
203
+ // Chat instructions textarea
204
+ const chatTextarea = document.getElementById('chat-instructions');
205
+ if (chatTextarea) {
206
+ chatTextarea.addEventListener('input', () => {
207
+ this.currentSettings.default_chat_instructions = chatTextarea.value;
208
+ this.updateChatCharCount(chatTextarea.value.length);
209
+ this.checkForChanges();
210
+ });
211
+ }
212
+
203
213
  // Analysis mode segmented control
204
214
  const modeToggle = document.getElementById('analysis-mode-toggle');
205
215
  if (modeToggle) {
@@ -814,7 +824,8 @@ class RepoSettingsPage {
814
824
  default_tab: settings.default_tab || 'single',
815
825
  default_council_id: settings.default_council_id || null,
816
826
  default_instructions: settings.default_instructions || '',
817
- local_path: settings.local_path || null
827
+ local_path: settings.local_path || null,
828
+ default_chat_instructions: settings.default_chat_instructions || ''
818
829
  };
819
830
 
820
831
  // Set current settings
@@ -832,7 +843,8 @@ class RepoSettingsPage {
832
843
  default_tab: 'single',
833
844
  default_council_id: null,
834
845
  default_instructions: '',
835
- local_path: null
846
+ local_path: null,
847
+ default_chat_instructions: ''
836
848
  };
837
849
  this.currentSettings = { ...this.originalSettings };
838
850
  this.updateUI();
@@ -887,6 +899,13 @@ class RepoSettingsPage {
887
899
  this.updateCharCount(textarea.value.length);
888
900
  }
889
901
 
902
+ // Update chat instructions textarea
903
+ const chatTextarea = document.getElementById('chat-instructions');
904
+ if (chatTextarea) {
905
+ chatTextarea.value = this.currentSettings.default_chat_instructions || '';
906
+ this.updateChatCharCount(chatTextarea.value.length);
907
+ }
908
+
890
909
  // Update local path display
891
910
  this.updateLocalPathDisplay();
892
911
  }
@@ -923,6 +942,13 @@ class RepoSettingsPage {
923
942
  }
924
943
  }
925
944
 
945
+ updateChatCharCount(count) {
946
+ const charCountEl = document.getElementById('chat-char-count');
947
+ if (charCountEl) {
948
+ charCountEl.textContent = count;
949
+ }
950
+ }
951
+
926
952
  checkForChanges() {
927
953
  // Use nullish coalescing to normalize null/undefined for consistent comparison
928
954
  const providerChanged = (this.currentSettings.default_provider ?? null) !== (this.originalSettings.default_provider ?? null);
@@ -930,8 +956,9 @@ class RepoSettingsPage {
930
956
  const tabChanged = (this.currentSettings.default_tab ?? 'single') !== (this.originalSettings.default_tab ?? 'single');
931
957
  const councilChanged = (this.currentSettings.default_council_id ?? null) !== (this.originalSettings.default_council_id ?? null);
932
958
  const instructionsChanged = (this.currentSettings.default_instructions ?? '') !== (this.originalSettings.default_instructions ?? '');
959
+ const chatInstructionsChanged = (this.currentSettings.default_chat_instructions ?? '') !== (this.originalSettings.default_chat_instructions ?? '');
933
960
 
934
- this.hasUnsavedChanges = providerChanged || modelChanged || tabChanged || councilChanged || instructionsChanged;
961
+ this.hasUnsavedChanges = providerChanged || modelChanged || tabChanged || councilChanged || instructionsChanged || chatInstructionsChanged;
935
962
 
936
963
  // Show/hide action bar
937
964
  const actionBar = document.getElementById('action-bar');
@@ -965,7 +992,8 @@ class RepoSettingsPage {
965
992
  default_model: this.currentSettings.default_model,
966
993
  default_tab: this.currentSettings.default_tab,
967
994
  default_council_id: this.currentSettings.default_council_id,
968
- default_instructions: this.currentSettings.default_instructions
995
+ default_instructions: this.currentSettings.default_instructions,
996
+ default_chat_instructions: this.currentSettings.default_chat_instructions
969
997
  })
970
998
  });
971
999
 
@@ -1039,7 +1067,8 @@ class RepoSettingsPage {
1039
1067
  default_tab: null,
1040
1068
  default_council_id: null,
1041
1069
  default_instructions: '',
1042
- local_path: null
1070
+ local_path: null,
1071
+ default_chat_instructions: ''
1043
1072
  })
1044
1073
  });
1045
1074
 
@@ -1054,7 +1083,8 @@ class RepoSettingsPage {
1054
1083
  default_tab: 'single',
1055
1084
  default_council_id: null,
1056
1085
  default_instructions: '',
1057
- local_path: null
1086
+ local_path: null,
1087
+ default_chat_instructions: ''
1058
1088
  };
1059
1089
  this.currentSettings = { ...this.originalSettings };
1060
1090
  this.hasUnsavedChanges = false;
@@ -0,0 +1,44 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ /**
3
+ * Canonical category-to-emoji mapping for AI suggestion types.
4
+ * Used by SuggestionManager and FileCommentManager to format adopted comments.
5
+ *
6
+ * Canonical types from src/ai/prompts/shared/output-schema.js:
7
+ * bug|improvement|praise|suggestion|design|performance|security|code-style
8
+ */
9
+
10
+ (function() {
11
+ const CATEGORY_EMOJI_MAP = {
12
+ 'bug': '\u{1F41B}', // bug
13
+ 'improvement': '\u{1F4A1}', // lightbulb
14
+ 'praise': '\u{2B50}', // star
15
+ 'suggestion': '\u{1F4AC}', // speech bubble
16
+ 'design': '\u{1F4D0}', // triangular ruler
17
+ 'performance': '\u{26A1}', // high voltage
18
+ 'security': '\u{1F512}', // lock
19
+ 'code-style': '\u{1F3A8}', // artist palette
20
+ 'style': '\u{1F3A8}' // artist palette (alias for code-style)
21
+ };
22
+
23
+ const DEFAULT_EMOJI = '\u{1F4AC}'; // speech bubble
24
+
25
+ /**
26
+ * Get emoji for a suggestion category
27
+ * @param {string} category - Category name
28
+ * @returns {string} Emoji character
29
+ */
30
+ function getEmoji(category) {
31
+ return CATEGORY_EMOJI_MAP[category] || DEFAULT_EMOJI;
32
+ }
33
+
34
+ // Export to global scope
35
+ window.CategoryEmoji = {
36
+ MAP: CATEGORY_EMOJI_MAP,
37
+ DEFAULT: DEFAULT_EMOJI,
38
+ getEmoji
39
+ };
40
+ })();
41
+
42
+ if (typeof module !== 'undefined' && module.exports) {
43
+ module.exports = { CATEGORY_EMOJI_MAP: window.CategoryEmoji.MAP, DEFAULT_EMOJI: window.CategoryEmoji.DEFAULT, getEmoji: window.CategoryEmoji.getEmoji };
44
+ }
@@ -0,0 +1,32 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ /**
3
+ * Timestamp parsing utility for consistent UTC interpretation.
4
+ *
5
+ * SQLite's CURRENT_TIMESTAMP produces strings like "2024-01-20 15:30:00"
6
+ * without a timezone indicator. JavaScript's Date() would interpret these
7
+ * as local time, but they are actually UTC. This helper ensures correct
8
+ * UTC parsing across the entire frontend.
9
+ */
10
+
11
+ (function () {
12
+ /**
13
+ * Parse a timestamp string, ensuring UTC interpretation for SQLite timestamps.
14
+ * @param {string} timestamp - Timestamp string (ISO 8601 or SQLite format)
15
+ * @returns {Date} Parsed Date object (Invalid Date when input is falsy)
16
+ */
17
+ function parseTimestamp(timestamp) {
18
+ if (!timestamp) return new Date(NaN);
19
+
20
+ // If the timestamp already has timezone info (ends with Z or +/-offset), parse as-is
21
+ if (/Z$|[+-]\d{2}:\d{2}$/.test(timestamp)) {
22
+ return new Date(timestamp);
23
+ }
24
+
25
+ // SQLite CURRENT_TIMESTAMP format: "YYYY-MM-DD HH:MM:SS" (no timezone, but is UTC)
26
+ // Append 'Z' to interpret as UTC
27
+ return new Date(timestamp + 'Z');
28
+ }
29
+
30
+ // Export to global scope
31
+ window.parseTimestamp = parseTimestamp;
32
+ })();
package/public/local.html CHANGED
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en" data-theme="light">
2
+ <html lang="en" data-theme="light" data-chat="disabled">
3
3
  <head>
4
4
  <script>
5
5
  // Initialize theme immediately to prevent flash
@@ -7,6 +7,16 @@
7
7
  const savedTheme = localStorage.getItem('theme') || 'light';
8
8
  document.documentElement.setAttribute('data-theme', savedTheme);
9
9
  })();
10
+ // Fetch chat availability early so PanelGroup sees the correct state
11
+ fetch('/api/config').then(r => r.ok ? r.json() : null).then(config => {
12
+ if (!config) return;
13
+ let state = 'disabled';
14
+ if (config.enable_chat) state = config.pi_available ? 'available' : 'unavailable';
15
+ document.documentElement.setAttribute('data-chat', state);
16
+ const shortcutsState = config.chat_enable_shortcuts === false ? 'disabled' : 'enabled';
17
+ document.documentElement.setAttribute('data-chat-shortcuts', shortcutsState);
18
+ window.dispatchEvent(new CustomEvent('chat-state-changed', { detail: { state } }));
19
+ }).catch(() => {});
10
20
  </script>
11
21
  <meta charset="UTF-8">
12
22
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -286,7 +296,7 @@
286
296
  <!-- File Tree Sidebar (Left) -->
287
297
  <aside class="sidebar files-sidebar" id="files-sidebar">
288
298
  <div class="sidebar-header">
289
- <span class="sidebar-title">Changed Files</span>
299
+ <span class="sidebar-title">File Navigator</span>
290
300
  <div style="display: flex; align-items: center; gap: 8px;">
291
301
  <span class="sidebar-count" id="sidebar-file-count">0</span>
292
302
  <button class="sidebar-collapse-btn" id="sidebar-collapse-btn" title="Close panel">
@@ -364,10 +374,19 @@
364
374
  </svg>
365
375
  <span class="btn-text">Analyze</span>
366
376
  </button>
367
- <button class="btn btn-sm btn-icon" id="ai-panel-toggle" title="Toggle Review panel">
377
+ <button class="btn btn-sm btn-icon" id="chat-toggle-btn" title="Toggle Chat panel">
378
+ <svg class="chat-icon" viewBox="0 0 16 16" fill="currentColor" width="14" height="14">
379
+ <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"/>
380
+ </svg>
381
+ </button>
382
+ <button class="btn btn-sm btn-icon" id="panel-layout-toggle" title="Switch panel layout" style="display: none;">
368
383
  <svg viewBox="0 0 16 16" fill="currentColor" width="14" height="14">
369
- <path d="M6.823 7.823a.25.25 0 0 1 0 .354l-2.396 2.396A.25.25 0 0 1 4 10.396V5.604a.25.25 0 0 1 .427-.177Z"></path>
370
- <path d="M1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0ZM1.5 1.75v12.5c0 .138.112.25.25.25H9.5v-13H1.75a.25.25 0 0 0-.25.25ZM11 14.5h3.25a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25H11Z"></path>
384
+ <path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25ZM1.5 1.75v12.5c0 .138.112.25.25.25H7.5v-13H1.75a.25.25 0 0 0-.25.25ZM8.5 14.5h5.75a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25H8.5Z"/>
385
+ </svg>
386
+ </button>
387
+ <button class="btn btn-sm btn-icon" id="ai-panel-toggle" title="Toggle Review panel">
388
+ <svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
389
+ <path d="M2 2h4a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1Zm4.655 8.595a.75.75 0 0 1 0 1.06L4.03 14.28a.75.75 0 0 1-1.06 0l-1.5-1.5a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l.97.97 2.095-2.095a.75.75 0 0 1 1.06 0ZM9.75 2.5h5.5a.75.75 0 0 1 0 1.5h-5.5a.75.75 0 0 1 0-1.5Zm0 5h5.5a.75.75 0 0 1 0 1.5h-5.5a.75.75 0 0 1 0-1.5Zm0 5h5.5a.75.75 0 0 1 0 1.5h-5.5a.75.75 0 0 1 0-1.5Zm-7.25-9v3h3v-3Z"/>
371
390
  </svg>
372
391
  </button>
373
392
  </div>
@@ -377,86 +396,90 @@
377
396
  </div>
378
397
  </main>
379
398
 
380
- <!-- AI Analysis Panel (Right) -->
381
- <aside class="ai-panel" id="ai-panel">
382
- <div class="resize-handle resize-handle-left" data-panel="ai-panel"></div>
383
- <div class="ai-panel-header">
384
- <div class="ai-panel-title">
385
- <button class="ai-avatar-small ai-summary-btn" id="ai-summary-btn" aria-label="View AI summary" title="View AI Summary">
386
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14">
387
- <path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"/>
388
- <path d="M18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456z"/>
399
+ <!-- Right Panel Group (AI Panel + Chat) -->
400
+ <div class="right-panel-group" id="right-panel-group">
401
+ <!-- AI Analysis Panel (Right) -->
402
+ <aside class="ai-panel" id="ai-panel">
403
+ <div class="resize-handle resize-handle-left" data-panel="ai-panel"></div>
404
+ <div class="ai-panel-header">
405
+ <div class="ai-panel-title">
406
+ <button class="ai-avatar-small ai-summary-btn" id="ai-summary-btn" aria-label="View AI summary" title="View AI Summary">
407
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="14" height="14">
408
+ <path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"/>
409
+ <path d="M18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456z"/>
410
+ </svg>
411
+ </button>
412
+ <span>Review</span>
413
+ </div>
414
+ <button class="ai-panel-close" id="ai-panel-close" title="Close panel">
415
+ <svg viewBox="0 0 16 16" fill="currentColor" width="14" height="14">
416
+ <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"/>
389
417
  </svg>
390
418
  </button>
391
- <span>Review</span>
392
419
  </div>
393
- <button class="ai-panel-close" id="ai-panel-close" title="Close panel">
394
- <svg viewBox="0 0 16 16" fill="currentColor" width="14" height="14">
395
- <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"/>
396
- </svg>
397
- </button>
398
- </div>
399
420
 
400
- <!-- Analysis Context Section -->
401
- <div class="analysis-context" id="analysis-context">
402
- <!-- Empty state -->
403
- <div class="analysis-context-empty" id="analysis-context-empty">
404
- <svg class="analysis-context-empty-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
405
- <path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"/>
406
- </svg>
407
- <span>No AI analysis yet</span>
408
- </div>
409
-
410
- <!-- Selector (hidden until analysis exists) -->
411
- <div class="analysis-context-selector" id="analysis-context-selector" style="display: none;">
412
- <button class="analysis-context-btn" id="analysis-context-btn">
413
- <span class="analysis-context-label" id="analysis-context-label">--</span>
414
- <svg class="dropdown-caret" width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
415
- <path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
421
+ <!-- Analysis Context Section -->
422
+ <div class="analysis-context" id="analysis-context">
423
+ <!-- Empty state -->
424
+ <div class="analysis-context-empty" id="analysis-context-empty">
425
+ <svg class="analysis-context-empty-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
426
+ <path d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"/>
416
427
  </svg>
417
- </button>
418
- </div>
428
+ <span>No AI analysis yet</span>
429
+ </div>
419
430
 
420
- <!-- Split-panel dropdown (hidden by default) -->
421
- <div class="analysis-context-dropdown" id="analysis-context-dropdown">
422
- <div class="analysis-run-list" id="analysis-context-list">
423
- <!-- Populated by JavaScript -->
431
+ <!-- Selector (hidden until analysis exists) -->
432
+ <div class="analysis-context-selector" id="analysis-context-selector" style="display: none;">
433
+ <button class="analysis-context-btn" id="analysis-context-btn">
434
+ <span class="analysis-context-label" id="analysis-context-label">--</span>
435
+ <svg class="dropdown-caret" width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
436
+ <path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
437
+ </svg>
438
+ </button>
424
439
  </div>
425
- <div class="analysis-preview-panel" id="analysis-context-preview">
426
- <!-- Populated by JavaScript on hover -->
440
+
441
+ <!-- Split-panel dropdown (hidden by default) -->
442
+ <div class="analysis-context-dropdown" id="analysis-context-dropdown">
443
+ <div class="analysis-run-list" id="analysis-context-list">
444
+ <!-- Populated by JavaScript -->
445
+ </div>
446
+ <div class="analysis-preview-panel" id="analysis-context-preview">
447
+ <!-- Populated by JavaScript on hover -->
448
+ </div>
427
449
  </div>
428
450
  </div>
429
- </div>
430
451
 
431
- <!-- Segment Control -->
432
- <div class="segment-control" id="segment-control">
433
- <div class="segment-control-inner">
434
- <button class="segment-btn active" data-segment="ai">AI <span class="segment-count">(0)</span></button>
435
- <button class="segment-btn" data-segment="comments">User <span class="segment-count">(0)</span></button>
436
- <button class="segment-btn" data-segment="all">All <span class="segment-count">(0)</span></button>
452
+ <!-- Segment Control -->
453
+ <div class="segment-control" id="segment-control">
454
+ <div class="segment-control-inner">
455
+ <button class="segment-btn active" data-segment="ai">AI <span class="segment-count">(0)</span></button>
456
+ <button class="segment-btn" data-segment="comments">User <span class="segment-count">(0)</span></button>
457
+ <button class="segment-btn" data-segment="all">All <span class="segment-count">(0)</span></button>
458
+ </div>
437
459
  </div>
438
- </div>
439
-
440
- <!-- Level Filter -->
441
- <div class="level-filter" id="level-filter">
442
- <button class="level-pill active" data-level="final" title="Curated suggestions from all levels">Overall</button>
443
- <button class="level-pill" data-level="1" title="Line-level analysis">Line</button>
444
- <button class="level-pill" data-level="2" title="File-level analysis">File</button>
445
- <button class="level-pill" data-level="3" title="Codebase-level analysis">Codebase</button>
446
- </div>
447
460
 
448
- <!-- Findings Summary -->
449
- <div class="findings-summary" id="findings-summary">
450
- <div class="findings-header">
451
- <span class="findings-count" id="findings-count">0 items</span>
461
+ <!-- Level Filter -->
462
+ <div class="level-filter" id="level-filter">
463
+ <button class="level-pill active" data-level="final" title="Curated suggestions from all levels">Overall</button>
464
+ <button class="level-pill" data-level="1" title="Line-level analysis">Line</button>
465
+ <button class="level-pill" data-level="2" title="File-level analysis">File</button>
466
+ <button class="level-pill" data-level="3" title="Codebase-level analysis">Codebase</button>
452
467
  </div>
453
- <div class="findings-list" id="findings-list">
454
- <div class="findings-empty">
455
- <p>No AI analysis yet. Click "Analyze" to get started.</p>
468
+
469
+ <!-- Findings Summary -->
470
+ <div class="findings-summary" id="findings-summary">
471
+ <div class="findings-header">
472
+ <span class="findings-count" id="findings-count">0 items</span>
473
+ </div>
474
+ <div class="findings-list" id="findings-list">
475
+ <div class="findings-empty">
476
+ <p>No AI analysis yet. Click "Analyze" to get started.</p>
477
+ </div>
456
478
  </div>
457
479
  </div>
458
- </div>
459
- </aside>
480
+ </aside>
481
+ <div id="chat-panel-container"></div>
482
+ </div>
460
483
  </div>
461
484
  </div>
462
485
 
@@ -476,12 +499,18 @@
476
499
  <!-- Tier icons utility -->
477
500
  <script src="/js/utils/tier-icons.js"></script>
478
501
 
502
+ <!-- Category emoji mapping -->
503
+ <script src="/js/utils/category-emoji.js"></script>
504
+
479
505
  <!-- Suggestion UI utility -->
480
506
  <script src="/js/utils/suggestion-ui.js"></script>
481
507
 
482
508
  <!-- File order utility -->
483
509
  <script src="/js/utils/file-order.js"></script>
484
510
 
511
+ <!-- Timestamp parsing utility -->
512
+ <script src="/js/utils/time.js"></script>
513
+
485
514
  <!-- Components -->
486
515
  <script src="/js/components/Toast.js"></script>
487
516
  <script src="/js/components/ConfirmDialog.js"></script>
@@ -512,6 +541,14 @@
512
541
  <script src="/js/modules/file-comment-manager.js"></script>
513
542
  <script src="/js/modules/panel-resizer.js"></script>
514
543
  <script src="/js/modules/analysis-history.js"></script>
544
+ <script src="/js/modules/diff-context.js"></script>
545
+ <script src="/js/modules/file-list-merger.js"></script>
546
+
547
+ <!-- Chat Panel Component -->
548
+ <script src="/js/components/ChatPanel.js"></script>
549
+
550
+ <!-- Panel Group Component -->
551
+ <script src="/js/components/PanelGroup.js"></script>
515
552
 
516
553
  <!-- Local Mode Configuration -->
517
554
  <script>