Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions build/dashboard/tests/frontend/components.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
44 changes: 7 additions & 37 deletions build/dashboard/tests/web/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 "—").
Expand All @@ -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"] == {
Expand Down
16 changes: 11 additions & 5 deletions docs/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down