diff --git a/CHANGELOG.md b/CHANGELOG.md index 36497a33..bcccae2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,28 @@ per the process in [`docs/dev/releasing.md`](docs/dev/releasing.md). ## [Unreleased] +### Added + +- Mine cart train (#748): a pixel-art strip under the hero band retelling the chart window as + cargo — one cart per interval, token coins for what landed (orange ɱ per share interval, a + pair for a found block, a purple gem per confirmed Tari payout, a blue X per XvB raffle win), + with a tipple, a sleeping cat, and drifting clouds. Static under reduced motion. `/api/state` + gains a `payouts` key: confirmed payouts per chain as `{x, amount}` timeline points, gated on + the payout-confirmation flags. + +### Changed + +- Earnings calculator standardized (#748): every tab presents its estimate in one + Day / Month / Year table with a shared-precision coin column and a `≈` fiat column once the + price is known. Tari gains month/year spans, XvB gains the published current-tier reward + table, Energy gains Revenue (est.) / Power Cost / Net columns. Net figures carry the one + judgment colour — green in profit, red in loss. +- Dashboard panel round (#748): stat-heavy cards open collapsed to their headline stats with a + "Show all" toggle, the Advanced grid gains Your Stack / The Wider Pool section labels, the + workers table gains tabular numerics + row hover with offline-red scoped to the rig name, + chart active chips move from green to accent, and the topology diagram gains zone tints and + marching-ants on live routes. + ## [1.12.0] - 2026-07-22 ### Added diff --git a/build/dashboard/tests/frontend/components.test.mjs b/build/dashboard/tests/frontend/components.test.mjs index b7b262bc..761edc2c 100644 --- a/build/dashboard/tests/frontend/components.test.mjs +++ b/build/dashboard/tests/frontend/components.test.mjs @@ -471,6 +471,37 @@ test('EarningsCard grows fiat rows + a price provenance line once a price is kno assert.match(html, /static, set in config\.json/); }); +test('EarningsCard shows Confirmed on-chain under the estimates on both tabs when payout confirmation is on (#381)', () => { + const s = clone(); + s.earnings.available = true; + s.earnings.tari_available = true; + s.earnings.tari_coeff_day = 2e-3; + s.earnings.confirmed = { + enabled: true, count: 3, xmr_24h: 0.25, xmr_7d: 0.75, xmr_all: 1.75, + last_ts: Math.floor(Date.now() / 1000) - 3600, + }; + s.earnings.tari_confirmed = { + enabled: true, count: 1, xtm_24h: 0, xtm_7d: 4552.15, xtm_all: 4552.15, + last_ts: Math.floor(Date.now() / 1000) - 7200, + }; + let html = renderApp({ state: s }); + // One confirmed block per tab, populated from the summary keys through the coin formatters. + assert.equal(html.split('Confirmed on-chain').length - 1, 2); + assert.match(html, /0\.250000 XMR/); // xmr_24h + assert.match(html, /1\.7500 XMR/); // xmr_all + assert.match(html, /4552\.1500 XTM/); // xtm_all + assert.match(html, /Last payout/); + // Estimates first, confirmed reality after — on the Tari tab the block follows the + // Long-run Average table, mirroring the Monero tab's order. + const tari = html.slice(html.indexOf('id="epanel-tari"'), html.indexOf('id="epanel-xvb"')); + assert.ok(tari.indexOf('Long-run Average') < tari.indexOf('Confirmed on-chain')); + // Confirmation off (the default) -> no confirmed block anywhere, estimates stand alone. + s.earnings.confirmed = { enabled: false }; + s.earnings.tari_confirmed = { enabled: false }; + html = renderApp({ state: s }); + assert.doesNotMatch(html, /Confirmed on-chain/); +}); + test('EarningsCard XvB tab shows the published current-tier reward as a day/month/year table', () => { const s = clone(); s.earnings.available = true; diff --git a/build/dashboard/tests/web/test_views.py b/build/dashboard/tests/web/test_views.py index 49d972d6..3a17bdd8 100644 --- a/build/dashboard/tests/web/test_views.py +++ b/build/dashboard/tests/web/test_views.py @@ -1552,6 +1552,11 @@ def test_payouts_ride_build_state_end_to_end(self, monkeypatch): assert st["payouts"]["tari"][0]["amount"] == 4552 # Only x + amount ship — no txid in the payload the browser sees. assert set(st["payouts"]["tari"][0]) == {"x", "amount"} + # The earnings card's confirmed summaries gate on the SAME flags in the same request — + # the timeline (mine cart) and the card can never disagree about whether payout + # confirmation is on for a chain. + assert st["earnings"]["tari_confirmed"]["enabled"] is True + assert st["earnings"]["confirmed"] == {"enabled": False} def test_build_disk_growth_shape_and_ms_epoch(self): now = time.time() @@ -1857,26 +1862,8 @@ def test_confirmed_disabled_by_default(self): e = build_earnings(self._NET, _metrics()) assert e["confirmed"] == {"enabled": False} - def test_confirmed_totals_windowed(self): - # #381: 24h / 7d / all-time XMR sums from stored confirmed payouts, atomic→XMR at the edge. - now = 1_000_000.0 - payouts = [ - {"txid": "a", "ts": now - 100, "amount_atomic": 250_000_000_000}, # in 24h - {"txid": "b", "ts": now - 3 * 86_400, "amount_atomic": 500_000_000_000}, # in 7d - { - "txid": "c", - "ts": now - 30 * 86_400, - "amount_atomic": 1_000_000_000_000, - }, # all-time only - ] - from mining_dashboard.web.views import _confirmed_payouts_summary - - s = _confirmed_payouts_summary(payouts, now=now) - assert s["enabled"] is True and s["count"] == 3 - assert s["xmr_24h"] == pytest.approx(0.25) - assert s["xmr_7d"] == pytest.approx(0.75) - assert s["xmr_all"] == pytest.approx(1.75) - assert s["last_ts"] == now - 100 + # ponytail: the 24h/7d/all windowing math is proven once, in TestConfirmedPayoutsSummary — + # this class only asserts build_earnings passes payouts through (enabled/empty/disabled). def test_confirmed_enabled_but_empty(self): # Feature on, nothing confirmed yet → enabled with zeroed totals (shows 0.000000, not "—"). @@ -1895,23 +1882,6 @@ def test_tari_confirmed_disabled_by_default(self): e = build_earnings(self._NET, _metrics()) assert e["tari_confirmed"] == {"enabled": False} - def test_tari_confirmed_totals_windowed_in_xtm(self): - # #462: XTM sums (microTari ÷1e6) with xtm_* keys, distinct from the monero xmr_* block. - now = 1_000_000.0 - tari_payouts = [ - {"txid": "a", "ts": now - 100, "amount_atomic": 250_000}, # in 24h - {"txid": "b", "ts": now - 3 * 86_400, "amount_atomic": 500_000}, # in 7d - {"txid": "c", "ts": now - 30 * 86_400, "amount_atomic": 1_000_000}, # all-time only - ] - from mining_dashboard.web.views import MICRO_PER_XTM, _confirmed_payouts_summary - - s = _confirmed_payouts_summary(tari_payouts, now=now, divisor=MICRO_PER_XTM, unit="xtm") - assert s["enabled"] is True and s["count"] == 3 - assert s["xtm_24h"] == pytest.approx(0.25) - assert s["xtm_7d"] == pytest.approx(0.75) - assert s["xtm_all"] == pytest.approx(1.75) - assert s["last_ts"] == now - 100 - def test_tari_confirmed_enabled_but_empty(self): e = build_earnings(self._NET, _metrics(), tari_payouts=[]) assert e["tari_confirmed"] == { diff --git a/docs/dashboard.md b/docs/dashboard.md index fa9348e5..f7674468 100644 --- a/docs/dashboard.md +++ b/docs/dashboard.md @@ -152,9 +152,12 @@ a token coin poking over the rim: | Blue X coin | An XvB raffle win. | Hover a cart for its exact interval and haul ("14:20–14:30 — 2 shares"). A cat sleeps by the -track; clouds drift past; the coins bob as the track rumbles. The train is decorative — -everything it shows, the chart above shows precisely — and all motion stops when your system -asks for reduced motion. It disappears while there is no history to show. +track; clouds drift past; the coins bob as the track rumbles. The train is decorative: shares, +blocks, and raffle wins all appear on the chart above with precise timestamps. The one thing +the train alone marks is the Tari gem coin — Tari payouts stay off the chart (see +[Payout confirmation](#payout-confirmation)), and the cart's hover tooltip is their timeline. +All motion stops when your system asks for reduced motion, and the strip disappears while +there is no history to show. ### Node status & failover @@ -187,7 +190,9 @@ needed. The database also keeps three smaller series: pool block-found events, hourly monerod-DB-size and host-disk-usage samples, and XvB-credited scalar samples taken roughly every 5 minutes. `/api/state` serves them as `blocks`, `disk_growth`, and `xvb_history`, range-filtered the same way as -`share_stats`. No chart reads them yet — this is persistence and API exposure only. +`share_stats`. The [mine cart train](#mine-cart-train) reads `blocks` and the chart's donation +overlay reads `xvb_history`; only `disk_growth` is persistence and API exposure alone, no +renderer yet. While a node is down, the dashboard rejects workers so they fail over to the backup pools you've configured, rather than sitting idle on a stack that can't mine. A sustained outage stops the @@ -509,7 +514,8 @@ landed — onto the hashrate chart, on the same marker row as the event diamonds Toggle it from the chart legend like any other series. When XvB is on, a dashed **XvB donation %** line overlays the chart on its own right-side 0–100% axis, drawn from the recorded donation history, so you can line a payout up against how much hashrate you were donating when it arrived. Tari -payouts are solo and lumpy, so they stay in the earnings card and off the chart. +payouts are solo and lumpy, so they stay in the earnings card and off the chart — the +[mine cart train](#mine-cart-train) marks each one with a purple gem coin instead. The dashboard polls the wallet on a slow cadence (about every 5 minutes) and records each confirmed payout to a small local table, so a restart never re-alerts. Coinbase outputs become spendable only