git-watchtower 1.14.13 → 1.14.15

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.14.13",
3
+ "version": "1.14.15",
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": {
@@ -41,6 +41,7 @@ let slotResultFlashFrame = 0; // Flash animation frame
41
41
  let slotResultInterval = null; // Interval for result display/flash
42
42
  let slotResultRenderCallback = null; // Callback for re-rendering
43
43
  let slotResultLabel = null; // "NOTHING", "WIN", "BIG WIN", "JACKPOT" etc
44
+ let slotResultClearTimeout = null; // No-win 2s auto-clear timer handle
44
45
 
45
46
  // Win animation state
46
47
  let winAnimationFrame = 0;
@@ -98,8 +99,45 @@ function enable() {
98
99
  function disable() {
99
100
  casinoEnabled = false;
100
101
  stopMarquee();
101
- stopSlotReels();
102
+ // resetSlotState — not stopSlotReels — because stopSlotReels() runs the
103
+ // end-of-poll result animation (labels a "NOTHING" panel and schedules a
104
+ // 2s auto-clear). Calling that during disable paints a brand-new casino
105
+ // effect on the way out, which the user just asked to stop.
106
+ resetSlotState();
102
107
  stopWinAnimation();
108
+ // resetLossState — stop the loss interval AND clear lossMessage. Without
109
+ // this, a triggerLoss() in flight kept lossAnimationInterval firing for
110
+ // up to ~15 frames × 120 ms = 1.8 s after disable, and lossMessage stayed
111
+ // set so isLossAnimating() reported true into the next session.
112
+ resetLossState();
113
+ }
114
+
115
+ /**
116
+ * Clear all slot reel state and timers without scheduling any new effects.
117
+ * Safe to call from disable() — unlike stopSlotReels(), it does not kick
118
+ * off a result display or a delayed clear timer.
119
+ * @private
120
+ */
121
+ function resetSlotState() {
122
+ if (slotReelInterval) {
123
+ clearInterval(slotReelInterval);
124
+ slotReelInterval = null;
125
+ }
126
+ if (slotResultInterval) {
127
+ clearInterval(slotResultInterval);
128
+ slotResultInterval = null;
129
+ }
130
+ if (slotResultClearTimeout) {
131
+ clearTimeout(slotResultClearTimeout);
132
+ slotResultClearTimeout = null;
133
+ }
134
+ isSpinning = false;
135
+ slotReelFrame = 0;
136
+ slotResult = null;
137
+ slotResultIsWin = false;
138
+ slotResultFlashFrame = 0;
139
+ slotResultLabel = null;
140
+ slotResultRenderCallback = null;
103
141
  }
104
142
 
105
143
  /**
@@ -312,8 +350,11 @@ function stopSlotReels(hadUpdates = false, renderCallback = null, winLevel = nul
312
350
  slotResult.push(SLOT_SYMBOLS[idx]);
313
351
  }
314
352
 
315
- // Display for 2 seconds then fade
316
- setTimeout(() => {
353
+ // Display for 2 seconds then fade. Store the handle so disable() /
354
+ // resetSlotState() can cancel it — otherwise a delayed clear fires
355
+ // mid-way through the next enabled session and nulls live state.
356
+ slotResultClearTimeout = setTimeout(() => {
357
+ slotResultClearTimeout = null;
317
358
  slotResult = null;
318
359
  slotResultLabel = null;
319
360
  if (slotResultRenderCallback) slotResultRenderCallback();
@@ -326,6 +367,7 @@ function stopSlotReels(hadUpdates = false, renderCallback = null, winLevel = nul
326
367
  * @returns {Object|null}
327
368
  */
328
369
  function getSlotResultLabel() {
370
+ if (!casinoEnabled) return null;
329
371
  return slotResultLabel;
330
372
  }
331
373
 
@@ -334,6 +376,7 @@ function getSlotResultLabel() {
334
376
  * @returns {boolean}
335
377
  */
336
378
  function isSlotSpinning() {
379
+ if (!casinoEnabled) return false;
337
380
  return isSpinning;
338
381
  }
339
382
 
@@ -342,6 +385,7 @@ function isSlotSpinning() {
342
385
  * @returns {boolean}
343
386
  */
344
387
  function hasSlotResult() {
388
+ if (!casinoEnabled) return false;
345
389
  return slotResult !== null;
346
390
  }
347
391
 
@@ -350,6 +394,7 @@ function hasSlotResult() {
350
394
  * @returns {boolean}
351
395
  */
352
396
  function isSlotsActive() {
397
+ if (!casinoEnabled) return false;
353
398
  return isSpinning || slotResult !== null;
354
399
  }
355
400
 
@@ -358,6 +403,10 @@ function isSlotsActive() {
358
403
  * @returns {string}
359
404
  */
360
405
  function getSlotReelDisplay() {
406
+ // Defense-in-depth: even if stale state survived a disable/enable cycle,
407
+ // don't paint casino UI when the mode is off.
408
+ if (!casinoEnabled) return '';
409
+
361
410
  // Show result if we have one
362
411
  if (slotResult) {
363
412
  const reels = [];
@@ -519,6 +568,18 @@ function stopLossAnimation() {
519
568
  }
520
569
  }
521
570
 
571
+ /**
572
+ * Clear all loss animation state without side effects. Used by disable()
573
+ * so a loss in flight at the moment casino mode is turned off doesn't
574
+ * keep its interval running or leave lossMessage set for isLossAnimating().
575
+ * @private
576
+ */
577
+ function resetLossState() {
578
+ stopLossAnimation();
579
+ lossMessage = null;
580
+ lossAnimationFrame = 0;
581
+ }
582
+
522
583
  /**
523
584
  * Get loss animation display
524
585
  * @param {number} width
@@ -542,6 +603,7 @@ function getLossDisplay(width) {
542
603
  * @returns {boolean}
543
604
  */
544
605
  function isLossAnimating() {
606
+ if (!casinoEnabled) return false;
545
607
  return lossMessage !== null;
546
608
  }
547
609