git-watchtower 1.10.5 → 1.10.6

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": "git-watchtower",
3
- "version": "1.10.5",
3
+ "version": "1.10.6",
4
4
  "description": "Terminal-based Git branch monitor with activity sparklines and optional dev server with live reload",
5
5
  "main": "bin/git-watchtower.js",
6
6
  "bin": {
@@ -33,26 +33,32 @@ function getDashboardJs() {
33
33
  'use strict';
34
34
 
35
35
  // ── State ──────────────────────────────────────────────────────
36
- let state = null;
37
- let prevBranches = null; // for notification diffing
38
- let selectedIndex = 0;
39
- let searchMode = false;
40
- let searchQuery = '';
41
- let confirmMode = false;
42
- let confirmCallback = null;
43
- let connected = false;
44
- let flashTimer = null;
45
- let activeTabId = null;
46
- let logViewerMode = false;
47
- let logViewerTab = 'server';
48
- let branchActionMode = false;
49
- let infoMode = false;
50
- let cleanupMode = false;
51
- let updateMode = false;
52
- let stashMode = false;
53
- let pendingStashBranch = null;
54
- let updateNotificationShown = false;
55
- let remoteTabPollTimer = null;
36
+ let state = null; // server-pushed state (branches, config, etc.)
37
+
38
+ // Client-side UI state — consolidated into a single object for
39
+ // easier debugging (inspect ui in console) and clearer separation
40
+ // from the server-pushed 'state' above.
41
+ const ui = {
42
+ prevBranches: null,
43
+ selectedIndex: 0,
44
+ searchMode: false,
45
+ searchQuery: '',
46
+ confirmMode: false,
47
+ confirmCallback: null,
48
+ connected: false,
49
+ flashTimer: null,
50
+ activeTabId: null,
51
+ logViewerMode: false,
52
+ logViewerTab: 'server',
53
+ branchActionMode: false,
54
+ infoMode: false,
55
+ cleanupMode: false,
56
+ updateMode: false,
57
+ stashMode: false,
58
+ pendingStashBranch: null,
59
+ updateNotificationShown: false,
60
+ remoteTabPollTimer: null,
61
+ };
56
62
 
57
63
  // ── Persistent Preferences (localStorage) ─────────────────────
58
64
  const PREFS_KEY = 'git-watchtower-prefs';
@@ -191,27 +197,27 @@ function getDashboardJs() {
191
197
  evtSource = new EventSource('/api/events');
192
198
 
193
199
  evtSource.onopen = () => {
194
- connected = true;
200
+ ui.connected = true;
195
201
  updateConnectionStatus();
196
202
  };
197
203
 
198
204
  evtSource.addEventListener('state', (e) => {
199
205
  try {
200
206
  const newState = JSON.parse(e.data);
201
- if (!activeTabId && newState.activeProjectId) {
202
- activeTabId = newState.activeProjectId;
207
+ if (!ui.activeTabId && newState.activeProjectId) {
208
+ ui.activeTabId = newState.activeProjectId;
203
209
  }
204
210
  // SSE always pushes the local project's state. When the user
205
211
  // is viewing a different tab we must NOT overwrite the per-project
206
212
  // data (branches, PRs, activity, etc.) — only update global
207
213
  // metadata so the tab bar, connection status, and version info
208
214
  // stay current.
209
- const viewingLocalProject = !activeTabId || activeTabId === newState.activeProjectId;
215
+ const viewingLocalProject = !ui.activeTabId || ui.activeTabId === newState.activeProjectId;
210
216
  if (viewingLocalProject) {
211
217
  if (state && state.branches) {
212
218
  diffBranchesForNotifications(state.branches, newState.branches || []);
213
219
  }
214
- prevBranches = state ? state.branches : null;
220
+ ui.prevBranches = state ? state.branches : null;
215
221
  state = newState;
216
222
  } else {
217
223
  if (state) {
@@ -240,7 +246,7 @@ function getDashboardJs() {
240
246
  try {
241
247
  const data = JSON.parse(e.data);
242
248
  if (!data.success && data.message && data.message.indexOf('uncommitted') !== -1) {
243
- pendingStashBranch = data.branch || null;
249
+ ui.pendingStashBranch = data.branch || null;
244
250
  showErrorToastWithHint(data.message, 'Press S to stash');
245
251
  } else {
246
252
  showToast(data.message, data.success ? 'success' : 'error');
@@ -249,7 +255,7 @@ function getDashboardJs() {
249
255
  });
250
256
 
251
257
  evtSource.onerror = () => {
252
- connected = false;
258
+ ui.connected = false;
253
259
  updateConnectionStatus();
254
260
  };
255
261
  }
@@ -257,8 +263,8 @@ function getDashboardJs() {
257
263
  function updateConnectionStatus() {
258
264
  const dot = document.getElementById('connection-dot');
259
265
  const badge = document.getElementById('status-badge');
260
- if (connected) {
261
- dot.className = 'connection-dot connected';
266
+ if (ui.connected) {
267
+ dot.className = 'connection-dot ui.connected';
262
268
  badge.className = 'badge badge-online';
263
269
  badge.textContent = 'live';
264
270
  } else {
@@ -274,7 +280,7 @@ function getDashboardJs() {
274
280
  xhr.open('POST', '/api/action');
275
281
  xhr.setRequestHeader('Content-Type', 'application/json');
276
282
  const data = { action, payload: payload || {} };
277
- if (activeTabId) data.projectId = activeTabId;
283
+ if (ui.activeTabId) data.projectId = ui.activeTabId;
278
284
  xhr.send(JSON.stringify(data));
279
285
  }
280
286
 
@@ -283,8 +289,8 @@ function getDashboardJs() {
283
289
  const el = document.getElementById('flash');
284
290
  el.textContent = text;
285
291
  el.className = 'flash visible ' + (type || 'info');
286
- clearTimeout(flashTimer);
287
- flashTimer = setTimeout(() => { el.className = 'flash'; }, 3000);
292
+ clearTimeout(ui.flashTimer);
293
+ ui.flashTimer = setTimeout(() => { el.className = 'flash'; }, 3000);
288
294
  }
289
295
 
290
296
  // ── Toast Notifications ────────────────────────────────────────
@@ -337,7 +343,7 @@ function getDashboardJs() {
337
343
  };
338
344
 
339
345
  function anyModalOpen() {
340
- return _openModals.length > 0 || confirmMode;
346
+ return _openModals.length > 0 || ui.confirmMode;
341
347
  }
342
348
 
343
349
  // Create modal instances
@@ -349,18 +355,18 @@ function getDashboardJs() {
349
355
  const updateModal = new Modal('update-overlay', 'update-close');
350
356
 
351
357
  // Per-modal hide callbacks for state cleanup
352
- logViewerModal.onHide = () => { logViewerMode = false; };
353
- branchActionModal.onHide = () => { branchActionMode = false; };
354
- infoModal.onHide = () => { infoMode = false; };
355
- stashModal.onHide = () => { stashMode = false; pendingStashBranch = null; };
356
- cleanupModal.onHide = () => { cleanupMode = false; };
357
- updateModal.onHide = () => { updateMode = false; };
358
+ logViewerModal.onHide = () => { ui.logViewerMode = false; };
359
+ branchActionModal.onHide = () => { ui.branchActionMode = false; };
360
+ infoModal.onHide = () => { ui.infoMode = false; };
361
+ stashModal.onHide = () => { ui.stashMode = false; ui.pendingStashBranch = null; };
362
+ cleanupModal.onHide = () => { ui.cleanupMode = false; };
363
+ updateModal.onHide = () => { ui.updateMode = false; };
358
364
 
359
365
  // ── Confirm Dialog ─────────────────────────────────────────────
360
366
  function showConfirm(title, message, onConfirm, opts) {
361
367
  opts = opts || {};
362
- confirmMode = true;
363
- confirmCallback = onConfirm;
368
+ ui.confirmMode = true;
369
+ ui.confirmCallback = onConfirm;
364
370
  const box = document.getElementById('confirm-box');
365
371
  box.innerHTML =
366
372
  '<div class="confirm-title">' + escHtml(title) + '</div>' +
@@ -375,13 +381,13 @@ function getDashboardJs() {
375
381
  document.getElementById('confirm-cancel').onclick = hideConfirm;
376
382
  document.getElementById('confirm-ok').onclick = () => {
377
383
  hideConfirm();
378
- if (confirmCallback) confirmCallback();
384
+ if (ui.confirmCallback) ui.confirmCallback();
379
385
  };
380
386
  }
381
387
 
382
388
  function hideConfirm() {
383
- confirmMode = false;
384
- confirmCallback = null;
389
+ ui.confirmMode = false;
390
+ ui.confirmCallback = null;
385
391
  document.getElementById('confirm-overlay').className = 'confirm-overlay';
386
392
  }
387
393
 
@@ -398,7 +404,7 @@ function getDashboardJs() {
398
404
  let html = '';
399
405
  for (let i = 0; i < projects.length; i++) {
400
406
  const p = projects[i];
401
- const isActive = p.id === activeTabId;
407
+ const isActive = p.id === ui.activeTabId;
402
408
  html += '<div class="tab' + (isActive ? ' active' : '') + '" data-project-id="' + escHtml(p.id) + '">';
403
409
  html += '<span class="tab-dot"></span>';
404
410
  html += escHtml(p.name);
@@ -412,7 +418,7 @@ function getDashboardJs() {
412
418
  const xhr = new XMLHttpRequest();
413
419
  xhr.open('GET', '/api/projects/' + projectId + '/state');
414
420
  xhr.onload = () => {
415
- if (xhr.status === 200 && activeTabId === projectId) {
421
+ if (xhr.status === 200 && ui.activeTabId === projectId) {
416
422
  try {
417
423
  const pState = JSON.parse(xhr.responseText);
418
424
  state.branches = pState.branches || [];
@@ -435,20 +441,20 @@ function getDashboardJs() {
435
441
  }
436
442
 
437
443
  function switchTab(projectId) {
438
- if (projectId === activeTabId) return;
439
- activeTabId = projectId;
440
- selectedIndex = 0;
441
- searchQuery = '';
442
- searchMode = false;
444
+ if (projectId === ui.activeTabId) return;
445
+ ui.activeTabId = projectId;
446
+ ui.selectedIndex = 0;
447
+ ui.searchQuery = '';
448
+ ui.searchMode = false;
443
449
  document.getElementById('search-bar').className = 'search-bar';
444
450
  document.getElementById('search-input').value = '';
445
451
  renderTabs();
446
452
  fetchAndApplyProjectState(projectId);
447
453
 
448
- clearInterval(remoteTabPollTimer);
449
- remoteTabPollTimer = null;
454
+ clearInterval(ui.remoteTabPollTimer);
455
+ ui.remoteTabPollTimer = null;
450
456
  if (state && projectId !== state.activeProjectId) {
451
- remoteTabPollTimer = setInterval(() => {
457
+ ui.remoteTabPollTimer = setInterval(() => {
452
458
  fetchAndApplyProjectState(projectId);
453
459
  }, 2000);
454
460
  }
@@ -464,7 +470,7 @@ ${pureFnBlock}
464
470
  getDisplayBranches = function() {
465
471
  if (!state || !state.branches) return [];
466
472
  return _pureGetDisplayBranches(state.branches, {
467
- searchQuery: searchQuery,
473
+ searchQuery: ui.searchQuery,
468
474
  pinnedBranches: pinnedBranches,
469
475
  sortOrder: sortOrder,
470
476
  });
@@ -487,7 +493,7 @@ ${pureFnBlock}
487
493
  if (state.version) versionEl.textContent = 'v' + state.version;
488
494
 
489
495
  // Status badge
490
- if (connected) {
496
+ if (ui.connected) {
491
497
  const badge = document.getElementById('status-badge');
492
498
  if (state.isOffline) {
493
499
  badge.className = 'badge badge-offline';
@@ -507,13 +513,13 @@ ${pureFnBlock}
507
513
  renderPrefsBar();
508
514
 
509
515
  // Auto-show update notification (once per session)
510
- if (state.updateAvailable && !updateNotificationShown && !anyModalOpen()) {
511
- updateNotificationShown = true;
516
+ if (state.updateAvailable && !ui.updateNotificationShown && !anyModalOpen()) {
517
+ ui.updateNotificationShown = true;
512
518
  showUpdateModal();
513
519
  }
514
520
 
515
521
  // Update log viewer if open
516
- if (logViewerMode) renderLogViewer();
522
+ if (ui.logViewerMode) renderLogViewer();
517
523
  }
518
524
 
519
525
  function renderBranches() {
@@ -522,14 +528,14 @@ ${pureFnBlock}
522
528
  const countEl = document.getElementById('branch-count');
523
529
  countEl.textContent = branches.length;
524
530
 
525
- if (selectedIndex >= branches.length) {
526
- selectedIndex = Math.max(0, branches.length - 1);
531
+ if (ui.selectedIndex >= branches.length) {
532
+ ui.selectedIndex = Math.max(0, branches.length - 1);
527
533
  }
528
534
 
529
535
  if (branches.length === 0) {
530
536
  container.innerHTML = '<div class="empty-state">' +
531
537
  '<div class="empty-state-icon">&#x1f33f;</div>' +
532
- (searchQuery ? 'No branches matching "' + escHtml(searchQuery) + '"' : 'No branches found') +
538
+ (ui.searchQuery ? 'No branches matching "' + escHtml(ui.searchQuery) + '"' : 'No branches found') +
533
539
  '</div>';
534
540
  return;
535
541
  }
@@ -537,7 +543,7 @@ ${pureFnBlock}
537
543
  let html = '';
538
544
  for (let i = 0; i < branches.length; i++) {
539
545
  const b = branches[i];
540
- const isSelected = i === selectedIndex;
546
+ const isSelected = i === ui.selectedIndex;
541
547
  const isCurrent = b.name === state.currentBranch;
542
548
 
543
549
  // Sparkline
@@ -668,8 +674,8 @@ ${pureFnBlock}
668
674
 
669
675
  // ── Log Viewer ─────────────────────────────────────────────────
670
676
  function showLogViewer() {
671
- logViewerMode = true;
672
- logViewerTab = 'server';
677
+ ui.logViewerMode = true;
678
+ ui.logViewerTab = 'server';
673
679
  renderLogViewer();
674
680
  logViewerModal.show();
675
681
  }
@@ -682,11 +688,11 @@ ${pureFnBlock}
682
688
  // Update tab active state
683
689
  const tabs = document.querySelectorAll('.log-viewer-tab');
684
690
  for (let t = 0; t < tabs.length; t++) {
685
- tabs[t].className = 'log-viewer-tab' + (tabs[t].getAttribute('data-tab') === logViewerTab ? ' active' : '');
691
+ tabs[t].className = 'log-viewer-tab' + (tabs[t].getAttribute('data-tab') === ui.logViewerTab ? ' active' : '');
686
692
  }
687
693
 
688
694
  let html = '';
689
- if (logViewerTab === 'server') {
695
+ if (ui.logViewerTab === 'server') {
690
696
  const logs = state.serverLogBuffer || [];
691
697
  if (logs.length === 0) {
692
698
  html = '<div style="color:var(--text-muted);padding:20px;text-align:center;">No server logs</div>';
@@ -721,16 +727,16 @@ ${pureFnBlock}
721
727
  document.getElementById('log-viewer-tabs').addEventListener('click', (e) => {
722
728
  const tab = e.target.closest('.log-viewer-tab');
723
729
  if (!tab) return;
724
- logViewerTab = tab.getAttribute('data-tab');
730
+ ui.logViewerTab = tab.getAttribute('data-tab');
725
731
  renderLogViewer();
726
732
  });
727
733
 
728
734
  // ── Branch Action Modal ────────────────────────────────────────
729
735
  function showBranchActions() {
730
736
  const branches = getDisplayBranches();
731
- if (!branches.length || selectedIndex >= branches.length) return;
732
- const branch = branches[selectedIndex];
733
- branchActionMode = true;
737
+ if (!branches.length || ui.selectedIndex >= branches.length) return;
738
+ const branch = branches[ui.selectedIndex];
739
+ ui.branchActionMode = true;
734
740
  branchActionModal.show();
735
741
  document.getElementById('branch-action-title').textContent = 'Actions: ' + branch.name;
736
742
 
@@ -836,7 +842,7 @@ ${pureFnBlock}
836
842
  // ── Info Panel ─────────────────────────────────────────────────
837
843
  function showInfo() {
838
844
  if (!state) return;
839
- infoMode = true;
845
+ ui.infoMode = true;
840
846
  const grid = document.getElementById('info-grid');
841
847
  const rows = [
842
848
  ['Project', state.projectName || '-'],
@@ -863,8 +869,8 @@ ${pureFnBlock}
863
869
 
864
870
  // ── Stash Management ───────────────────────────────────────────
865
871
  function showStashDialog(pendingBranch) {
866
- stashMode = true;
867
- pendingStashBranch = pendingBranch || null;
872
+ ui.stashMode = true;
873
+ ui.pendingStashBranch = pendingBranch || null;
868
874
  const msg = pendingBranch
869
875
  ? 'You have uncommitted changes. Stash them before switching to <strong>' + escHtml(pendingBranch) + '</strong>?'
870
876
  : 'Stash all uncommitted changes in the working directory?';
@@ -877,7 +883,7 @@ ${pureFnBlock}
877
883
  stashModal.show();
878
884
  document.getElementById('stash-cancel').onclick = hideStash;
879
885
  document.getElementById('stash-confirm').onclick = () => {
880
- sendAction('stash', { pendingBranch: pendingStashBranch });
886
+ sendAction('stash', { pendingBranch: ui.pendingStashBranch });
881
887
  showToast('Stashing changes...', 'info');
882
888
  hideStash();
883
889
  };
@@ -887,7 +893,7 @@ ${pureFnBlock}
887
893
 
888
894
  // ── Branch Cleanup ─────────────────────────────────────────────
889
895
  function showCleanup() {
890
- cleanupMode = true;
896
+ ui.cleanupMode = true;
891
897
  const html = '<div style="color:var(--text-dim);font-size:13px;margin-bottom:12px;">Scanning for branches with deleted remotes...</div>';
892
898
  document.getElementById('cleanup-content').innerHTML = html;
893
899
  cleanupModal.show();
@@ -950,7 +956,7 @@ ${pureFnBlock}
950
956
  // ── Update Notification ────────────────────────────────────────
951
957
  function showUpdateModal() {
952
958
  if (!state || !state.updateAvailable) return;
953
- updateMode = true;
959
+ ui.updateMode = true;
954
960
  const html = '<div class="update-versions">';
955
961
  html += '<span class="old-version">v' + escHtml(state.version || '?') + '</span>';
956
962
  html += '<span class="arrow">&#x2192;</span>';
@@ -1029,7 +1035,7 @@ ${pureFnBlock}
1029
1035
  hintEl.addEventListener('click', (e) => {
1030
1036
  const h = e.currentTarget.getAttribute('data-hint');
1031
1037
  if (h === 'Press S to stash') {
1032
- showStashDialog(pendingStashBranch);
1038
+ showStashDialog(ui.pendingStashBranch);
1033
1039
  }
1034
1040
  toast.classList.remove('visible');
1035
1041
  setTimeout(() => { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 300);
@@ -1056,10 +1062,10 @@ ${pureFnBlock}
1056
1062
  }
1057
1063
 
1058
1064
  // Log viewer tab switching
1059
- if (logViewerMode) {
1065
+ if (ui.logViewerMode) {
1060
1066
  if (e.key === 'Tab' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
1061
1067
  e.preventDefault();
1062
- logViewerTab = logViewerTab === 'server' ? 'activity' : 'server';
1068
+ ui.logViewerTab = ui.logViewerTab === 'server' ? 'activity' : 'server';
1063
1069
  renderLogViewer();
1064
1070
  }
1065
1071
  return;
@@ -1069,11 +1075,11 @@ ${pureFnBlock}
1069
1075
  if (_openModals.length > 0) return;
1070
1076
 
1071
1077
  // Confirm dialog mode — Escape to cancel, Enter to confirm
1072
- if (confirmMode) {
1078
+ if (ui.confirmMode) {
1073
1079
  if (e.key === 'Escape') { e.preventDefault(); hideConfirm(); }
1074
1080
  if (e.key === 'Enter') {
1075
1081
  e.preventDefault();
1076
- const cb = confirmCallback;
1082
+ const cb = ui.confirmCallback;
1077
1083
  hideConfirm();
1078
1084
  if (cb) cb();
1079
1085
  }
@@ -1081,20 +1087,20 @@ ${pureFnBlock}
1081
1087
  }
1082
1088
 
1083
1089
  // Search mode
1084
- if (searchMode) {
1090
+ if (ui.searchMode) {
1085
1091
  if (e.key === 'Escape') {
1086
1092
  e.preventDefault();
1087
- searchMode = false;
1088
- searchQuery = '';
1093
+ ui.searchMode = false;
1094
+ ui.searchQuery = '';
1089
1095
  document.getElementById('search-bar').className = 'search-bar';
1090
1096
  document.getElementById('search-input').value = '';
1091
- selectedIndex = 0;
1097
+ ui.selectedIndex = 0;
1092
1098
  renderBranches();
1093
1099
  return;
1094
1100
  }
1095
1101
  if (e.key === 'Enter') {
1096
1102
  e.preventDefault();
1097
- searchMode = false;
1103
+ ui.searchMode = false;
1098
1104
  document.getElementById('search-bar').className = 'search-bar';
1099
1105
  return;
1100
1106
  }
@@ -1125,7 +1131,7 @@ ${pureFnBlock}
1125
1131
  // Tab cycling with Tab key
1126
1132
  if (e.key === 'Tab' && projects.length > 1) {
1127
1133
  e.preventDefault();
1128
- const curIdx = projects.findIndex((p) => p.id === activeTabId);
1134
+ const curIdx = projects.findIndex((p) => p.id === ui.activeTabId);
1129
1135
  const nextIdx = e.shiftKey
1130
1136
  ? (curIdx - 1 + projects.length) % projects.length
1131
1137
  : (curIdx + 1) % projects.length;
@@ -1148,8 +1154,8 @@ ${pureFnBlock}
1148
1154
  case 'Enter':
1149
1155
  e.preventDefault();
1150
1156
  const branches = getDisplayBranches();
1151
- if (branches.length > 0 && selectedIndex < branches.length) {
1152
- const b = branches[selectedIndex];
1157
+ if (branches.length > 0 && ui.selectedIndex < branches.length) {
1158
+ const b = branches[ui.selectedIndex];
1153
1159
  if (b.isDeleted) {
1154
1160
  showToast('Cannot switch to a deleted branch', 'error');
1155
1161
  } else if (b.name === state.currentBranch) {
@@ -1162,9 +1168,9 @@ ${pureFnBlock}
1162
1168
  break;
1163
1169
  case '/':
1164
1170
  e.preventDefault();
1165
- searchMode = true;
1166
- searchQuery = '';
1167
- selectedIndex = 0;
1171
+ ui.searchMode = true;
1172
+ ui.searchQuery = '';
1173
+ ui.selectedIndex = 0;
1168
1174
  document.getElementById('search-bar').className = 'search-bar active';
1169
1175
  const input = document.getElementById('search-input');
1170
1176
  input.value = '';
@@ -1259,16 +1265,16 @@ ${pureFnBlock}
1259
1265
 
1260
1266
  // Search input handler
1261
1267
  document.getElementById('search-input').addEventListener('input', (e) => {
1262
- searchQuery = e.target.value;
1263
- selectedIndex = 0;
1268
+ ui.searchQuery = e.target.value;
1269
+ ui.selectedIndex = 0;
1264
1270
  renderBranches();
1265
1271
  });
1266
1272
 
1267
1273
  function moveSelection(delta) {
1268
1274
  const branches = getDisplayBranches();
1269
- const newIndex = selectedIndex + delta;
1275
+ const newIndex = ui.selectedIndex + delta;
1270
1276
  if (newIndex >= 0 && newIndex < branches.length) {
1271
- selectedIndex = newIndex;
1277
+ ui.selectedIndex = newIndex;
1272
1278
  renderBranches();
1273
1279
  }
1274
1280
  }
@@ -1279,7 +1285,7 @@ ${pureFnBlock}
1279
1285
  if (!item) return;
1280
1286
  const idx = parseInt(item.getAttribute('data-index'), 10);
1281
1287
  if (isNaN(idx)) return;
1282
- selectedIndex = idx;
1288
+ ui.selectedIndex = idx;
1283
1289
  renderBranches();
1284
1290
 
1285
1291
  // Double-click to switch with confirmation
@@ -1339,8 +1345,8 @@ ${pureFnBlock}
1339
1345
  }
1340
1346
  if (e.target.id === 'pin-selected-btn') {
1341
1347
  const branches = getDisplayBranches();
1342
- if (branches.length > 0 && selectedIndex < branches.length) {
1343
- const bn = branches[selectedIndex].name;
1348
+ if (branches.length > 0 && ui.selectedIndex < branches.length) {
1349
+ const bn = branches[ui.selectedIndex].name;
1344
1350
  const idx = pinnedBranches.indexOf(bn);
1345
1351
  if (idx === -1) {
1346
1352
  pinnedBranches.push(bn);