@in-the-loop-labs/pair-review 3.0.6 → 3.1.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 (81) hide show
  1. package/package.json +2 -1
  2. package/plugin/.claude-plugin/plugin.json +1 -1
  3. package/plugin-code-critic/.claude-plugin/plugin.json +1 -1
  4. package/plugin-code-critic/skills/analyze/references/level1-balanced.md +8 -0
  5. package/plugin-code-critic/skills/analyze/references/level1-fast.md +7 -0
  6. package/plugin-code-critic/skills/analyze/references/level1-thorough.md +8 -0
  7. package/plugin-code-critic/skills/analyze/references/level2-balanced.md +9 -0
  8. package/plugin-code-critic/skills/analyze/references/level2-fast.md +8 -0
  9. package/plugin-code-critic/skills/analyze/references/level2-thorough.md +9 -0
  10. package/plugin-code-critic/skills/analyze/references/level3-balanced.md +9 -0
  11. package/plugin-code-critic/skills/analyze/references/level3-fast.md +8 -0
  12. package/plugin-code-critic/skills/analyze/references/level3-thorough.md +9 -0
  13. package/plugin-code-critic/skills/analyze/references/orchestration-balanced.md +9 -0
  14. package/plugin-code-critic/skills/analyze/references/orchestration-fast.md +5 -0
  15. package/plugin-code-critic/skills/analyze/references/orchestration-thorough.md +9 -0
  16. package/public/css/analysis-config.css +103 -0
  17. package/public/css/pr.css +191 -4
  18. package/public/index.html +20 -0
  19. package/public/js/components/AIPanel.js +1 -1
  20. package/public/js/components/AdvancedConfigTab.js +87 -9
  21. package/public/js/components/AnalysisConfigModal.js +206 -5
  22. package/public/js/components/ChatPanel.js +22 -5
  23. package/public/js/components/CouncilProgressModal.js +241 -23
  24. package/public/js/components/TimeoutSelect.js +2 -0
  25. package/public/js/components/VoiceCentricConfigTab.js +183 -13
  26. package/public/js/index.js +119 -1
  27. package/public/js/local.js +166 -51
  28. package/public/js/modules/suggestion-manager.js +2 -1
  29. package/public/js/pr.js +71 -12
  30. package/public/js/repo-settings.js +2 -2
  31. package/public/local.html +32 -11
  32. package/public/pr.html +2 -0
  33. package/src/ai/analyzer.js +371 -111
  34. package/src/ai/claude-provider.js +2 -0
  35. package/src/ai/codex-provider.js +1 -1
  36. package/src/ai/copilot-provider.js +2 -0
  37. package/src/ai/executable-provider.js +538 -0
  38. package/src/ai/gemini-provider.js +2 -0
  39. package/src/ai/index.js +9 -1
  40. package/src/ai/pi-provider.js +10 -8
  41. package/src/ai/prompts/baseline/consolidation/balanced.js +54 -2
  42. package/src/ai/prompts/baseline/consolidation/fast.js +31 -1
  43. package/src/ai/prompts/baseline/consolidation/thorough.js +46 -3
  44. package/src/ai/prompts/baseline/level1/balanced.js +12 -0
  45. package/src/ai/prompts/baseline/level1/fast.js +11 -0
  46. package/src/ai/prompts/baseline/level1/thorough.js +12 -0
  47. package/src/ai/prompts/baseline/level2/balanced.js +13 -0
  48. package/src/ai/prompts/baseline/level2/fast.js +12 -0
  49. package/src/ai/prompts/baseline/level2/thorough.js +13 -0
  50. package/src/ai/prompts/baseline/level3/balanced.js +13 -0
  51. package/src/ai/prompts/baseline/level3/fast.js +12 -0
  52. package/src/ai/prompts/baseline/level3/thorough.js +13 -0
  53. package/src/ai/prompts/baseline/orchestration/balanced.js +15 -0
  54. package/src/ai/prompts/baseline/orchestration/fast.js +11 -0
  55. package/src/ai/prompts/baseline/orchestration/thorough.js +15 -0
  56. package/src/ai/prompts/render-for-skill.js +3 -0
  57. package/src/ai/prompts/shared/output-schema.js +8 -0
  58. package/src/ai/provider.js +91 -4
  59. package/src/chat/prompt-builder.js +39 -4
  60. package/src/chat/session-manager.js +32 -28
  61. package/src/config.js +15 -2
  62. package/src/database.js +59 -15
  63. package/src/git/base-branch.js +113 -29
  64. package/src/github/parser.js +1 -1
  65. package/src/local-review.js +15 -9
  66. package/src/local-scope.js +83 -0
  67. package/src/main.js +3 -2
  68. package/src/routes/analyses.js +34 -8
  69. package/src/routes/chat.js +15 -8
  70. package/src/routes/config.js +3 -120
  71. package/src/routes/councils.js +15 -6
  72. package/src/routes/executable-analysis.js +494 -0
  73. package/src/routes/local.js +152 -15
  74. package/src/routes/mcp.js +9 -4
  75. package/src/routes/pr.js +166 -29
  76. package/src/routes/reviews.js +31 -5
  77. package/src/routes/shared.js +72 -5
  78. package/src/routes/worktrees.js +4 -2
  79. package/src/utils/comment-formatter.js +28 -11
  80. package/src/utils/instructions.js +22 -8
  81. package/src/utils/logger.js +20 -10
package/public/css/pr.css CHANGED
@@ -501,16 +501,19 @@
501
501
  }
502
502
 
503
503
  /* Refresh button spinner animation */
504
- #refresh-pr .spinner-icon {
504
+ #refresh-pr .spinner-icon,
505
+ #local-refresh-btn .spinner-icon {
505
506
  display: none;
506
507
  animation: spin 1s linear infinite;
507
508
  }
508
509
 
509
- #refresh-pr.refreshing .refresh-icon {
510
+ #refresh-pr.refreshing .refresh-icon,
511
+ #local-refresh-btn.refreshing .refresh-icon {
510
512
  display: none !important;
511
513
  }
512
514
 
513
- #refresh-pr.refreshing .spinner-icon {
515
+ #refresh-pr.refreshing .spinner-icon,
516
+ #local-refresh-btn.refreshing .spinner-icon {
514
517
  display: inline-block !important;
515
518
  }
516
519
 
@@ -1871,6 +1874,8 @@ tr.newly-expanded .d2h-code-line-ctn {
1871
1874
  display: flex;
1872
1875
  align-items: center;
1873
1876
  gap: 8px;
1877
+ min-width: 0;
1878
+ overflow: hidden;
1874
1879
  }
1875
1880
 
1876
1881
  /* Category label badge for AI suggestions - HIDDEN in favor of unified AI Suggestion badge (issue pair_review-pqb) */
@@ -1889,11 +1894,16 @@ tr.newly-expanded .d2h-code-line-ctn {
1889
1894
  font-weight: 600;
1890
1895
  background: var(--ai-subtle, rgba(245, 158, 11, 0.1));
1891
1896
  color: var(--ai-primary, #f59e0b);
1897
+ white-space: nowrap;
1898
+ flex-shrink: 1;
1899
+ min-width: 0;
1900
+ overflow: hidden;
1892
1901
  }
1893
1902
 
1894
1903
  .ai-suggestion-badge svg {
1895
1904
  width: 14px;
1896
1905
  height: 14px;
1906
+ flex-shrink: 0;
1897
1907
  opacity: 0.8;
1898
1908
  }
1899
1909
 
@@ -1924,6 +1934,7 @@ tr.newly-expanded .d2h-code-line-ctn {
1924
1934
  background: var(--ai-subtle, rgba(245, 158, 11, 0.1));
1925
1935
  border-radius: var(--radius-sm, 4px);
1926
1936
  flex-shrink: 0;
1937
+ white-space: nowrap;
1927
1938
  }
1928
1939
 
1929
1940
  /* Type-specific colors - using CSS variables for theme support */
@@ -2074,6 +2085,10 @@ tr.newly-expanded .d2h-code-line-ctn {
2074
2085
  .ai-title {
2075
2086
  font-weight: 600;
2076
2087
  color: var(--color-text-primary);
2088
+ overflow: hidden;
2089
+ text-overflow: ellipsis;
2090
+ white-space: nowrap;
2091
+ min-width: 0;
2077
2092
  }
2078
2093
 
2079
2094
  /* Confidence display removed to match cleaner prototype design (issue pair_review-7g5) */
@@ -6466,6 +6481,96 @@ body:not([data-theme="dark"]) .theme-icon-light {
6466
6481
  text-overflow: ellipsis;
6467
6482
  }
6468
6483
 
6484
+ /* Accessible visually-hidden utility */
6485
+ .sr-only {
6486
+ position: absolute;
6487
+ width: 1px;
6488
+ height: 1px;
6489
+ padding: 0;
6490
+ margin: -1px;
6491
+ overflow: hidden;
6492
+ clip: rect(0, 0, 0, 0);
6493
+ white-space: nowrap;
6494
+ border: 0;
6495
+ }
6496
+
6497
+ /* Base branch selector */
6498
+ .toolbar-base-branch-wrap {
6499
+ display: none;
6500
+ align-items: center;
6501
+ }
6502
+
6503
+ .toolbar-base-branch-wrap:not([hidden]) {
6504
+ display: inline-flex;
6505
+ }
6506
+
6507
+ .base-branch-selector-wrap {
6508
+ display: none;
6509
+ align-items: center;
6510
+ }
6511
+
6512
+ .base-branch-selector-wrap:not([hidden]) {
6513
+ display: inline-flex;
6514
+ }
6515
+
6516
+ .base-branch-vs {
6517
+ font-size: 0.75rem;
6518
+ color: var(--color-text-tertiary);
6519
+ margin: 0 6px;
6520
+ }
6521
+
6522
+ .toolbar-base-branch-static[hidden] {
6523
+ display: none;
6524
+ }
6525
+
6526
+ .toolbar-base-branch-static {
6527
+ display: inline-flex;
6528
+ align-items: center;
6529
+ gap: 4px;
6530
+ padding: 2px 8px;
6531
+ background-color: var(--color-bg-tertiary);
6532
+ border: 1px solid var(--color-border-primary);
6533
+ border-radius: 6px;
6534
+ font-family: var(--font-mono);
6535
+ font-size: 11px;
6536
+ color: var(--color-text-primary);
6537
+ white-space: nowrap;
6538
+ overflow: hidden;
6539
+ text-overflow: ellipsis;
6540
+ max-width: 260px;
6541
+ }
6542
+
6543
+ .toolbar-base-branch-static svg {
6544
+ color: var(--color-text-tertiary);
6545
+ flex-shrink: 0;
6546
+ }
6547
+
6548
+ .toolbar-base-branch-static span {
6549
+ overflow: hidden;
6550
+ text-overflow: ellipsis;
6551
+ }
6552
+
6553
+ .base-branch-select {
6554
+ font-size: 11px;
6555
+ padding: 2px 8px;
6556
+ background: var(--color-bg-tertiary);
6557
+ border: 1px solid var(--color-border-primary);
6558
+ border-radius: 6px;
6559
+ color: var(--color-text-primary);
6560
+ cursor: pointer;
6561
+ max-width: 260px;
6562
+ font-family: var(--font-mono);
6563
+ }
6564
+
6565
+ .base-branch-select:hover {
6566
+ background: var(--color-bg-secondary);
6567
+ }
6568
+
6569
+ .base-branch-select:focus {
6570
+ outline: 2px solid var(--color-accent-primary);
6571
+ outline-offset: -2px;
6572
+ }
6573
+
6469
6574
  /* Shared copy button styles for toolbar (branch + commit) */
6470
6575
  .toolbar-copy-btn {
6471
6576
  display: inline-flex;
@@ -8943,12 +9048,24 @@ body.resizing * {
8943
9048
  }
8944
9049
 
8945
9050
  /* Category label for AI findings */
9051
+ .finding-meta {
9052
+ display: flex;
9053
+ align-items: center;
9054
+ gap: 4px;
9055
+ }
9056
+
8946
9057
  .finding-category {
8947
9058
  font-size: 0.6875rem;
8948
9059
  color: var(--color-text-tertiary);
8949
9060
  text-transform: capitalize;
8950
9061
  }
8951
9062
 
9063
+ .finding-meta .severity-badge {
9064
+ margin-left: 0;
9065
+ font-size: 0.6rem;
9066
+ padding: 0 4px;
9067
+ }
9068
+
8952
9069
  /* Star icon for praise findings */
8953
9070
  .finding-star {
8954
9071
  display: flex;
@@ -9659,7 +9776,7 @@ body.resizing * {
9659
9776
  .instructions-container {
9660
9777
  display: flex;
9661
9778
  flex-direction: column;
9662
- gap: 8px;
9779
+ gap: 4px;
9663
9780
  }
9664
9781
 
9665
9782
  .repo-instructions-banner {
@@ -10186,6 +10303,18 @@ body.resizing * {
10186
10303
  min-width: 90px;
10187
10304
  }
10188
10305
 
10306
+ .executable-note {
10307
+ font-size: 11px;
10308
+ color: var(--fg-muted, #656d76);
10309
+ font-style: italic;
10310
+ white-space: nowrap;
10311
+ }
10312
+
10313
+ .vc-levels-disabled-note {
10314
+ margin-top: 12px;
10315
+ margin-bottom: 0;
10316
+ }
10317
+
10189
10318
  .btn-icon {
10190
10319
  box-sizing: border-box;
10191
10320
  width: 28px;
@@ -12758,3 +12887,61 @@ body.resizing * {
12758
12887
  opacity: 0.4;
12759
12888
  cursor: not-allowed;
12760
12889
  }
12890
+
12891
+ /* Severity badges for executable provider suggestions */
12892
+ .severity-badge {
12893
+ display: inline-flex;
12894
+ font-size: 11px;
12895
+ padding: 1px 6px;
12896
+ border-radius: 3px;
12897
+ font-weight: 500;
12898
+ margin-left: 4px;
12899
+ white-space: nowrap;
12900
+ flex-shrink: 0;
12901
+ width: fit-content;
12902
+ }
12903
+
12904
+ .severity-critical {
12905
+ background: #fde8e8;
12906
+ color: #d32f2f;
12907
+ }
12908
+
12909
+ .severity-medium {
12910
+ background: #fff3e0;
12911
+ color: #ef6c00;
12912
+ }
12913
+
12914
+ .severity-minor {
12915
+ background: #fff8e1;
12916
+ color: #f9a825;
12917
+ }
12918
+
12919
+ [data-theme="dark"] .severity-critical {
12920
+ background: #3d1c1c;
12921
+ color: #ef9a9a;
12922
+ }
12923
+
12924
+ [data-theme="dark"] .severity-medium {
12925
+ background: #3d2e1c;
12926
+ color: #ffcc80;
12927
+ }
12928
+
12929
+ [data-theme="dark"] .severity-minor {
12930
+ background: #3d3a1c;
12931
+ color: #fff176;
12932
+ }
12933
+
12934
+ /* Note shown when executable provider is selected (no level toggles) */
12935
+ .executable-provider-note {
12936
+ padding: 8px 12px;
12937
+ background: var(--bg-tertiary, #f6f8fa);
12938
+ border-radius: 6px;
12939
+ font-size: 13px;
12940
+ color: var(--fg-muted, #656d76);
12941
+ margin-top: 8px;
12942
+ }
12943
+
12944
+ [data-theme="dark"] .executable-provider-note {
12945
+ background: rgba(56, 139, 253, 0.1);
12946
+ color: var(--color-text-secondary, #8b949e);
12947
+ }
package/public/index.html CHANGED
@@ -685,6 +685,25 @@
685
685
  text-align: right;
686
686
  }
687
687
 
688
+ /* Analysis-in-progress spinner for index page rows */
689
+ .index-analysis-spinner {
690
+ display: inline-block;
691
+ width: 12px;
692
+ height: 12px;
693
+ border: 2px solid var(--ai-primary);
694
+ border-top-color: transparent;
695
+ border-radius: 50%;
696
+ animation: index-spin 0.8s linear infinite;
697
+ margin-right: 6px;
698
+ vertical-align: middle;
699
+ flex-shrink: 0;
700
+ }
701
+
702
+ @keyframes index-spin {
703
+ 0% { transform: rotate(0deg); }
704
+ 100% { transform: rotate(360deg); }
705
+ }
706
+
688
707
  .local-table .btn-repo-settings {
689
708
  margin-right: 4px;
690
709
  }
@@ -1430,6 +1449,7 @@
1430
1449
  </div>
1431
1450
  </div>
1432
1451
 
1452
+ <script src="/js/ws-client.js"></script>
1433
1453
  <script src="/js/components/Toast.js"></script>
1434
1454
  <script src="/js/index.js"></script>
1435
1455
  </body>
@@ -1219,7 +1219,7 @@ class AIPanel {
1219
1219
  ${indicator}
1220
1220
  <div class="finding-content">
1221
1221
  <span class="finding-title">${this.escapeHtml(title)}</span>
1222
- ${category ? `<span class="finding-category">${this.escapeHtml(category)}</span>` : ''}
1222
+ ${category || finding.severity ? `<span class="finding-meta">${category ? `<span class="finding-category">${this.escapeHtml(category)}</span>` : ''}${finding.severity ? `<span class="severity-badge severity-${finding.severity}">${this.escapeHtml(finding.severity.toUpperCase())}</span>` : ''}</span>` : ''}
1223
1223
  ${fileName ? `<span class="finding-location">${this.escapeHtml(fileName)}</span>` : ''}
1224
1224
  </div>
1225
1225
  </button>
@@ -289,7 +289,7 @@ class AdvancedConfigTab {
289
289
  '2': { enabled: true, voices: [] },
290
290
  '3': { enabled: true, voices: [] }
291
291
  },
292
- consolidation: { provider: this._defaultProvider || 'claude', model: this._defaultModel || 'sonnet', tier: 'balanced', timeout: AdvancedConfigTab.DEFAULT_TIMEOUT }
292
+ consolidation: { provider: this._defaultProvider || 'claude', model: this._defaultModel || 'sonnet', tier: 'balanced', timeout: this._getProviderDefaultTimeout(this._defaultProvider || 'claude') }
293
293
  };
294
294
  }
295
295
 
@@ -605,10 +605,11 @@ class AdvancedConfigTab {
605
605
  }
606
606
  });
607
607
 
608
- // Provider change -> update model dropdowns
608
+ // Provider change -> update model dropdowns + timeout default
609
609
  panel.addEventListener('change', (e) => {
610
610
  if (e.target.classList.contains('voice-provider')) {
611
611
  this._updateModelDropdown(e.target);
612
+ this._applyProviderDefaultTimeout(e.target);
612
613
  }
613
614
  // Model change -> update tier to match model's recommended tier
614
615
  if (e.target.classList.contains('voice-model')) {
@@ -704,10 +705,13 @@ class AdvancedConfigTab {
704
705
 
705
706
  _populateProviderDropdown(select) {
706
707
  const currentValue = select.value;
708
+ const isConsolidation = select.dataset.target === 'orchestration';
707
709
  select.innerHTML = '';
708
710
  const providerIds = Object.keys(this.providers).filter(id => {
709
711
  const p = this.providers[id];
710
- return !p.availability || p.availability.available;
712
+ if (p.availability && !p.availability.available) return false;
713
+ if (isConsolidation && p.capabilities?.consolidation === false) return false;
714
+ return true;
711
715
  }).sort((a, b) => (this.providers[a].name || a).localeCompare(this.providers[b].name || b));
712
716
 
713
717
  for (const id of providerIds) {
@@ -805,6 +809,7 @@ class AdvancedConfigTab {
805
809
  const newProviderSelect = voiceList.querySelector(`.voice-provider[data-level="${level}"][data-index="${index}"]`);
806
810
  if (newProviderSelect) {
807
811
  this._populateProviderDropdown(newProviderSelect);
812
+ this._applyProviderDefaultTimeout(newProviderSelect);
808
813
  }
809
814
 
810
815
  // Update remove button visibility for this level
@@ -894,6 +899,16 @@ class AdvancedConfigTab {
894
899
  iconBtn.classList.toggle('has-instructions', hasContent);
895
900
  }
896
901
 
902
+ /**
903
+ * Get the default timeout for a provider, falling back to the static DEFAULT_TIMEOUT.
904
+ * @param {string} providerId - Provider ID (e.g., 'pi', 'claude')
905
+ * @returns {number} Default timeout in ms
906
+ */
907
+ _getProviderDefaultTimeout(providerId) {
908
+ const provider = this.providers[providerId];
909
+ return provider?.defaultTimeout ?? AdvancedConfigTab.DEFAULT_TIMEOUT;
910
+ }
911
+
897
912
  /**
898
913
  * Update the clock/timeout icon styling to indicate non-default timeout.
899
914
  * @param {Element} panel - The council panel element
@@ -906,7 +921,9 @@ class AdvancedConfigTab {
906
921
  const iconBtn = wrapper?.querySelector(`.toggle-timeout-icon[data-level="${level}"][data-index="${index}"]`);
907
922
  if (!iconBtn) return;
908
923
 
909
- const isNonDefault = parseInt(value, 10) !== AdvancedConfigTab.DEFAULT_TIMEOUT;
924
+ const providerId = wrapper?.querySelector('.voice-provider')?.value;
925
+ const defaultTimeout = this._getProviderDefaultTimeout(providerId);
926
+ const isNonDefault = parseInt(value, 10) !== defaultTimeout;
910
927
  iconBtn.classList.toggle('has-custom-timeout', isNonDefault);
911
928
  }
912
929
 
@@ -914,7 +931,10 @@ class AdvancedConfigTab {
914
931
  const iconBtn = panel.querySelector('#adv-orchestration-timeout-toggle');
915
932
  if (!iconBtn) return;
916
933
 
917
- const isNonDefault = parseInt(value, 10) !== AdvancedConfigTab.DEFAULT_TIMEOUT;
934
+ const orchRow = panel.querySelector('#orchestration-voice');
935
+ const providerId = orchRow?.querySelector('.voice-provider')?.value;
936
+ const defaultTimeout = this._getProviderDefaultTimeout(providerId);
937
+ const isNonDefault = parseInt(value, 10) !== defaultTimeout;
918
938
  iconBtn.classList.toggle('has-custom-timeout', isNonDefault);
919
939
  }
920
940
 
@@ -929,6 +949,48 @@ class AdvancedConfigTab {
929
949
  iconBtn.classList.toggle('has-instructions', hasContent);
930
950
  }
931
951
 
952
+ /**
953
+ * When a voice's provider changes, update its timeout to the new provider's default,
954
+ * preserving explicit user overrides via Math.max when the user had customized the value.
955
+ * @param {HTMLSelectElement} providerSelect - The provider dropdown that changed
956
+ */
957
+ _applyProviderDefaultTimeout(providerSelect) {
958
+ const panel = this.modal.querySelector('#tab-panel-advanced');
959
+ if (!panel) return;
960
+
961
+ const providerId = providerSelect.value;
962
+ const newDefault = this._getProviderDefaultTimeout(providerId);
963
+ const oldProviderId = providerSelect.dataset.previousProvider;
964
+ const oldDefault = oldProviderId ? this._getProviderDefaultTimeout(oldProviderId) : null;
965
+
966
+ const isOrchestration = providerSelect.dataset.target === 'orchestration';
967
+ if (isOrchestration) {
968
+ const timeoutEl = panel.querySelector('#adv-orchestration-timeout');
969
+ if (timeoutEl) {
970
+ const currentValue = parseInt(timeoutEl.value, 10);
971
+ const resolvedTimeout = (oldDefault !== null && currentValue !== oldDefault)
972
+ ? Math.max(currentValue, newDefault)
973
+ : newDefault;
974
+ timeoutEl.value = String(resolvedTimeout);
975
+ this._updateOrchestrationTimeoutIcon(panel, String(resolvedTimeout));
976
+ }
977
+ } else {
978
+ const { level, index } = providerSelect.dataset;
979
+ const wrapper = providerSelect.closest('.participant-wrapper');
980
+ const timeoutEl = wrapper?.querySelector('.adv-timeout');
981
+ if (timeoutEl) {
982
+ const currentValue = parseInt(timeoutEl.value, 10);
983
+ const resolvedTimeout = (oldDefault !== null && currentValue !== oldDefault)
984
+ ? Math.max(currentValue, newDefault)
985
+ : newDefault;
986
+ timeoutEl.value = String(resolvedTimeout);
987
+ this._updateTimeoutIcon(panel, level, index, String(resolvedTimeout));
988
+ }
989
+ }
990
+
991
+ providerSelect.dataset.previousProvider = providerId;
992
+ }
993
+
932
994
  // --- Dirty state tracking ---
933
995
 
934
996
  _markDirty() {
@@ -1108,6 +1170,7 @@ class AdvancedConfigTab {
1108
1170
  if (providerSelect) {
1109
1171
  this._populateProviderDropdown(providerSelect);
1110
1172
  providerSelect.value = voice.provider;
1173
+ providerSelect.dataset.previousProvider = voice.provider;
1111
1174
  this._updateModelDropdown(providerSelect);
1112
1175
  const modelSelect = row.querySelector('.voice-model');
1113
1176
  if (modelSelect) modelSelect.value = voice.model;
@@ -1121,13 +1184,17 @@ class AdvancedConfigTab {
1121
1184
  TimeoutSelect.mount(mount, { className: 'adv-timeout', title: 'Per-reviewer timeout' });
1122
1185
  }
1123
1186
  const timeoutEl = row?.querySelector('.adv-timeout');
1187
+ const providerDefaultTimeout = this._getProviderDefaultTimeout(voice.provider);
1124
1188
  if (timeoutEl && voice.timeout) {
1125
1189
  timeoutEl.value = String(voice.timeout);
1126
- // Show the dropdown if non-default
1127
- if (voice.timeout !== AdvancedConfigTab.DEFAULT_TIMEOUT) {
1190
+ // Show the dropdown if non-default for this provider
1191
+ if (voice.timeout !== providerDefaultTimeout) {
1128
1192
  timeoutEl.style.display = '';
1129
1193
  }
1130
1194
  this._updateTimeoutIcon(panel, String(level), String(i), String(voice.timeout));
1195
+ } else if (timeoutEl) {
1196
+ // No saved timeout — apply the provider's default
1197
+ timeoutEl.value = String(providerDefaultTimeout);
1131
1198
  }
1132
1199
 
1133
1200
  if (voice.customInstructions) {
@@ -1156,6 +1223,7 @@ class AdvancedConfigTab {
1156
1223
  if (providerSelect) {
1157
1224
  this._populateProviderDropdown(providerSelect);
1158
1225
  providerSelect.value = consolSection.provider;
1226
+ providerSelect.dataset.previousProvider = consolSection.provider;
1159
1227
  this._updateModelDropdown(providerSelect);
1160
1228
  const modelSelect = orchRow.querySelector('.voice-model');
1161
1229
  if (modelSelect) modelSelect.value = consolSection.model;
@@ -1166,13 +1234,18 @@ class AdvancedConfigTab {
1166
1234
 
1167
1235
  // Restore consolidation timeout
1168
1236
  const orchTimeoutSelect = panel.querySelector('#adv-orchestration-timeout');
1237
+ const orchProviderDefaultTimeout = this._getProviderDefaultTimeout(consolSection.provider);
1169
1238
  if (orchTimeoutSelect && consolSection.timeout) {
1170
1239
  orchTimeoutSelect.value = String(consolSection.timeout);
1171
- // Show the dropdown if non-default
1172
- if (consolSection.timeout !== AdvancedConfigTab.DEFAULT_TIMEOUT) {
1240
+ // Show the dropdown if non-default for this provider
1241
+ if (consolSection.timeout !== orchProviderDefaultTimeout) {
1173
1242
  orchTimeoutSelect.style.display = '';
1174
1243
  }
1175
1244
  this._updateOrchestrationTimeoutIcon(panel, String(consolSection.timeout));
1245
+ } else if (orchTimeoutSelect) {
1246
+ // No saved timeout — apply the provider's default
1247
+ orchTimeoutSelect.value = String(orchProviderDefaultTimeout);
1248
+ this._updateOrchestrationTimeoutIcon(panel, String(orchProviderDefaultTimeout));
1176
1249
  }
1177
1250
 
1178
1251
  // Restore consolidation custom instructions
@@ -1266,6 +1339,11 @@ class AdvancedConfigTab {
1266
1339
  }
1267
1340
  this._markClean();
1268
1341
  await this.loadCouncils();
1342
+ const selector = this.modal.querySelector('#council-selector');
1343
+ if (selector) {
1344
+ selector.value = this.selectedCouncilId;
1345
+ selector.classList.remove('new-council-selected');
1346
+ }
1269
1347
  }
1270
1348
 
1271
1349
  /**