git-watchtower 1.10.2 → 1.10.3

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.2",
3
+ "version": "1.10.3",
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": {
@@ -8,13 +8,13 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node bin/git-watchtower.js",
11
- "test": "node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/integration/**/*.test.js",
12
- "test:unit": "node --require ./tests/setup.js --test tests/unit/**/*.test.js",
11
+ "test": "node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/unit/**/**/*.test.js tests/integration/**/*.test.js",
12
+ "test:unit": "node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/unit/**/**/*.test.js",
13
13
  "test:integration": "node --require ./tests/setup.js --test tests/integration/**/*.test.js",
14
- "test:watch": "node --require ./tests/setup.js --test --watch tests/unit/**/*.test.js",
15
- "test:coverage": "c8 --reporter=text --reporter=html --reporter=lcov node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/integration/**/*.test.js",
16
- "test:coverage:text": "c8 --reporter=text node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/integration/**/*.test.js",
17
- "test:coverage:html": "c8 --reporter=html node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/integration/**/*.test.js && echo 'Coverage report: coverage/index.html'",
14
+ "test:watch": "node --require ./tests/setup.js --test --watch tests/unit/**/*.test.js tests/unit/**/**/*.test.js",
15
+ "test:coverage": "c8 --reporter=text --reporter=html --reporter=lcov node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/unit/**/**/*.test.js tests/integration/**/*.test.js",
16
+ "test:coverage:text": "c8 --reporter=text node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/unit/**/**/*.test.js tests/integration/**/*.test.js",
17
+ "test:coverage:html": "c8 --reporter=html node --require ./tests/setup.js --test tests/unit/**/*.test.js tests/unit/**/**/*.test.js tests/integration/**/*.test.js && echo 'Coverage report: coverage/index.html'",
18
18
  "typecheck": "tsc --noEmit"
19
19
  },
20
20
  "devDependencies": {
@@ -22,6 +22,7 @@
22
22
  "@semantic-release/git": "^10.0.1",
23
23
  "@types/node": "^22.0.0",
24
24
  "c8": "^10.1.2",
25
+ "jsdom": "^24.1.3",
25
26
  "semantic-release": "^25.0.3",
26
27
  "typescript": "^5.7.0"
27
28
  },
@@ -34,6 +34,7 @@ function getDashboardJs() {
34
34
  var stashMode = false;
35
35
  var pendingStashBranch = null;
36
36
  var updateNotificationShown = false;
37
+ var remoteTabPollTimer = null;
37
38
 
38
39
  // ── Persistent Preferences (localStorage) ─────────────────────
39
40
  var PREFS_KEY = 'git-watchtower-prefs';
@@ -187,14 +188,33 @@ function getDashboardJs() {
187
188
  evtSource.addEventListener('state', function(e) {
188
189
  try {
189
190
  var newState = JSON.parse(e.data);
190
- // Diff branches for desktop notifications
191
- if (state && state.branches) {
192
- diffBranchesForNotifications(state.branches, newState.branches || []);
191
+ if (!activeTabId && newState.activeProjectId) {
192
+ activeTabId = newState.activeProjectId;
193
193
  }
194
- prevBranches = state ? state.branches : null;
195
- state = newState;
196
- if (!activeTabId && state.activeProjectId) {
197
- activeTabId = state.activeProjectId;
194
+ // SSE always pushes the local project's state. When the user
195
+ // is viewing a different tab we must NOT overwrite the per-project
196
+ // data (branches, PRs, activity, etc.) — only update global
197
+ // metadata so the tab bar, connection status, and version info
198
+ // stay current.
199
+ var viewingLocalProject = !activeTabId || activeTabId === newState.activeProjectId;
200
+ if (viewingLocalProject) {
201
+ // Diff branches for desktop notifications
202
+ if (state && state.branches) {
203
+ diffBranchesForNotifications(state.branches, newState.branches || []);
204
+ }
205
+ prevBranches = state ? state.branches : null;
206
+ state = newState;
207
+ } else {
208
+ // Viewing a remote tab — preserve per-project fields, update globals only
209
+ if (state) {
210
+ state.projects = newState.projects;
211
+ state.version = newState.version;
212
+ state.updateAvailable = newState.updateAvailable;
213
+ state.updateInProgress = newState.updateInProgress;
214
+ state.clientCount = newState.clientCount;
215
+ } else {
216
+ state = newState;
217
+ }
198
218
  }
199
219
  renderTabs();
200
220
  render();
@@ -331,23 +351,17 @@ function getDashboardJs() {
331
351
  tabBar.innerHTML = html;
332
352
  }
333
353
 
334
- function switchTab(projectId) {
335
- if (projectId === activeTabId) return;
336
- activeTabId = projectId;
337
- selectedIndex = 0;
338
- searchQuery = '';
339
- searchMode = false;
340
- document.getElementById('search-bar').className = 'search-bar';
341
- document.getElementById('search-input').value = '';
342
- renderTabs();
343
- // Fetch the project's state
354
+ /**
355
+ * Fetch a project's state from the server and merge it into the
356
+ * current client-side state for rendering.
357
+ */
358
+ function fetchAndApplyProjectState(projectId) {
344
359
  var xhr = new XMLHttpRequest();
345
360
  xhr.open('GET', '/api/projects/' + projectId + '/state');
346
361
  xhr.onload = function() {
347
- if (xhr.status === 200) {
362
+ if (xhr.status === 200 && activeTabId === projectId) {
348
363
  try {
349
364
  var pState = JSON.parse(xhr.responseText);
350
- // Merge into current state for rendering
351
365
  state.branches = pState.branches || [];
352
366
  state.currentBranch = pState.currentBranch;
353
367
  state.activityLog = pState.activityLog || [];
@@ -359,6 +373,7 @@ function getDashboardJs() {
359
373
  state.pollingStatus = pState.pollingStatus || 'idle';
360
374
  state.isOffline = pState.isOffline || false;
361
375
  state.serverMode = pState.serverMode || 'none';
376
+ state.repoWebUrl = pState.repoWebUrl || null;
362
377
  render();
363
378
  } catch (err) { /* ignore */ }
364
379
  }
@@ -366,6 +381,28 @@ function getDashboardJs() {
366
381
  xhr.send();
367
382
  }
368
383
 
384
+ function switchTab(projectId) {
385
+ if (projectId === activeTabId) return;
386
+ activeTabId = projectId;
387
+ selectedIndex = 0;
388
+ searchQuery = '';
389
+ searchMode = false;
390
+ document.getElementById('search-bar').className = 'search-bar';
391
+ document.getElementById('search-input').value = '';
392
+ renderTabs();
393
+ fetchAndApplyProjectState(projectId);
394
+
395
+ // For non-local tabs the SSE stream won't push per-project updates,
396
+ // so poll the server periodically to keep the view fresh.
397
+ clearInterval(remoteTabPollTimer);
398
+ remoteTabPollTimer = null;
399
+ if (state && projectId !== state.activeProjectId) {
400
+ remoteTabPollTimer = setInterval(function() {
401
+ fetchAndApplyProjectState(projectId);
402
+ }, 2000);
403
+ }
404
+ }
405
+
369
406
  // ── Time Formatting ────────────────────────────────────────────
370
407
  function timeAgo(dateStr) {
371
408
  if (!dateStr) return '';