git-watchtower 2.3.7 → 2.3.8

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": "2.3.7",
3
+ "version": "2.3.8",
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": {
@@ -699,6 +699,24 @@ function getStats() {
699
699
  };
700
700
  }
701
701
 
702
+ /**
703
+ * Stats payload safe for SSE serialization. Excludes the random/clock-driven
704
+ * decorative fields (luckMeter, houseEdge, vibesQuality, timeSinceLastHit)
705
+ * because they tick on every call — leaving them in the payload defeats
706
+ * web.js's `lastPushedJson` dedup, so the full ~100 KB state is shipped to
707
+ * every connected client every 500 ms even when nothing has actually
708
+ * changed. The dashboard recomputes these client-side at render time.
709
+ *
710
+ * Stable but slow-ticking fields (sessionDuration, which changes once per
711
+ * minute) are kept — the cost is at most one extra push per minute.
712
+ *
713
+ * @returns {Object}
714
+ */
715
+ function getSerializableStats() {
716
+ const { luckMeter, houseEdge, vibesQuality, timeSinceLastHit, ...stable } = getStats();
717
+ return stable;
718
+ }
719
+
702
720
  /**
703
721
  * Reset session stats
704
722
  */
@@ -788,6 +806,7 @@ module.exports = {
788
806
  // Stats
789
807
  recordPoll,
790
808
  getStats,
809
+ getSerializableStats,
791
810
  resetStats,
792
811
  getStatsDisplay,
793
812
  };
@@ -1241,7 +1241,31 @@ ${pureFnBlock}
1241
1241
  return left + right;
1242
1242
  }
1243
1243
 
1244
+ // Decorative casino fields the server intentionally does NOT include in
1245
+ // the SSE payload because they're random/clock-driven and would defeat
1246
+ // the lastPushedJson dedup. Recompute them locally at render time so the
1247
+ // dashboard stays visually identical to the TUI.
1248
+ const CASINO_VIBES = ['\u{1f60e}','\u{1f525}','✨','\u{1f4ab}','\u{1f31f}','⚡','\u{1f3af}','\u{1f4aa}','\u{1f680}','\u{1f4af}'];
1249
+ function computeCasinoDecorations(cs) {
1250
+ const baseLuck = 50 + Math.random() * 30;
1251
+ const streakBonus = Math.min((cs.consecutivePolls || 0) * 5, 20);
1252
+ return {
1253
+ luckMeter: Math.min(Math.round(baseLuck + streakBonus), 99),
1254
+ houseEdge: Math.round(55 + Math.random() * 45),
1255
+ vibesQuality: CASINO_VIBES[Math.floor(Date.now() / 3000) % CASINO_VIBES.length],
1256
+ };
1257
+ }
1258
+
1244
1259
  function renderCasinoStatsRow(cs) {
1260
+ // Server-supplied (preferred) OR locally computed (default). The
1261
+ // fallback covers older servers that still ship the decorative fields,
1262
+ // plus tests that stub them in explicitly. != null treats undefined and
1263
+ // null as "missing" so legitimate zero values from the server survive.
1264
+ const local = computeCasinoDecorations(cs);
1265
+ const houseEdge = (cs.houseEdge != null) ? cs.houseEdge : local.houseEdge;
1266
+ const luckMeter = (cs.luckMeter != null) ? cs.luckMeter : local.luckMeter;
1267
+ const vibesQuality = cs.vibesQuality || local.vibesQuality;
1268
+
1245
1269
  const netClass = cs.netWinnings >= 0 ? 'pos' : 'neg';
1246
1270
  const netSign = cs.netWinnings >= 0 ? '+' : '';
1247
1271
  const stat = (k, v) => '<span class="stat"><span class="stat-k">' + k + '</span><span class="stat-v">' + v + '</span></span>';
@@ -1256,9 +1280,9 @@ ${pureFnBlock}
1256
1280
  let right = '<div class="stats-group">';
1257
1281
  right += stat('\u{1f4b8} Cost', '<span class="neg">' + dollar + (cs.totalPolls || 0) + '</span>');
1258
1282
  right += stat('\u{1f4b0} Net', '<span class="' + netClass + '">' + netSign + dollar + (cs.netWinnings || 0) + '</span>');
1259
- right += stat('\u{1f3b0} Edge', '<span class="neon">' + (cs.houseEdge || 0) + '%</span>');
1260
- right += stat('\u{1f3b2} Luck', '<span class="gold">' + (cs.luckMeter || 0) + '%</span>');
1261
- right += stat('\u{1f60e} Vibes', escHtml(cs.vibesQuality || ''));
1283
+ right += stat('\u{1f3b0} Edge', '<span class="neon">' + houseEdge + '%</span>');
1284
+ right += stat('\u{1f3b2} Luck', '<span class="gold">' + luckMeter + '%</span>');
1285
+ right += stat('\u{1f60e} Vibes', escHtml(vibesQuality));
1262
1286
  right += stat('\u{1f9e0} Hits', '<span class="pos">' + (cs.dopamineHits || 0) + '</span>');
1263
1287
  if (cs.consecutivePolls > 1) right += stat('\u{1f525} Streak', '<span class="gold">' + cs.consecutivePolls + 'x</span>');
1264
1288
  right += '</div>';
package/src/server/web.js CHANGED
@@ -147,8 +147,12 @@ class WebDashboardServer {
147
147
  // the mode on, so the web dashboard can render the same winnings box
148
148
  // the terminal does. Null when disabled — keeps payload small and
149
149
  // avoids ticking Math.random()/Date.now() into every SSE push when
150
- // nobody's asked for the effect.
151
- casinoStats: s.casinoModeEnabled ? casino.getStats() : null,
150
+ // nobody's asked for the effect. We use `getSerializableStats` (not
151
+ // the full getStats) so the random/clock-driven decorative fields
152
+ // (luckMeter, houseEdge, vibesQuality, timeSinceLastHit) don't
153
+ // defeat the lastPushedJson dedup — the dashboard recomputes them
154
+ // client-side from the stable counters that ARE in the payload.
155
+ casinoStats: s.casinoModeEnabled ? casino.getSerializableStats() : null,
152
156
  projectName: s.projectName,
153
157
 
154
158
  // Activity