git-watchtower 2.3.7 → 2.3.9
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 +1 -1
- package/src/casino/index.js +19 -0
- package/src/server/web-ui/js.js +27 -3
- package/src/server/web.js +6 -2
- package/src/ui/renderer.js +8 -3
package/package.json
CHANGED
package/src/casino/index.js
CHANGED
|
@@ -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
|
};
|
package/src/server/web-ui/js.js
CHANGED
|
@@ -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">' +
|
|
1260
|
-
right += stat('\u{1f3b2} Luck', '<span class="gold">' +
|
|
1261
|
-
right += stat('\u{1f60e} Vibes', escHtml(
|
|
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
|
-
|
|
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
|
package/src/ui/renderer.js
CHANGED
|
@@ -1505,13 +1505,19 @@ function renderUpdateModal(state, write) {
|
|
|
1505
1505
|
lines.push(updateCmd);
|
|
1506
1506
|
lines.push('');
|
|
1507
1507
|
|
|
1508
|
+
// Tracks where the selectable option block starts so the highlight
|
|
1509
|
+
// logic below can reverse-map line index → option index. -1 when
|
|
1510
|
+
// there are no options (the updateInProgress path). Computed from
|
|
1511
|
+
// `lines.length` so adding any line above never silently desyncs the
|
|
1512
|
+
// highlight — previously this was a hardcoded `7` that happened to
|
|
1513
|
+
// match the line count above by coincidence.
|
|
1514
|
+
let optionStartIdx = -1;
|
|
1508
1515
|
if (state.updateInProgress) {
|
|
1509
1516
|
lines.push('Updating...');
|
|
1510
1517
|
lines.push('');
|
|
1511
1518
|
lines.push('Please wait...');
|
|
1512
1519
|
} else {
|
|
1513
|
-
|
|
1514
|
-
const optionStartIdx = lines.length;
|
|
1520
|
+
optionStartIdx = lines.length;
|
|
1515
1521
|
for (const opt of options) {
|
|
1516
1522
|
lines.push(opt);
|
|
1517
1523
|
}
|
|
@@ -1519,7 +1525,6 @@ function renderUpdateModal(state, write) {
|
|
|
1519
1525
|
lines.push('[Enter] Select [Esc] Dismiss');
|
|
1520
1526
|
}
|
|
1521
1527
|
|
|
1522
|
-
const optionStartIdx = state.updateInProgress ? -1 : 7;
|
|
1523
1528
|
const height = lines.length + 2;
|
|
1524
1529
|
|
|
1525
1530
|
// Draw magenta double-border box
|