src/live.rs reads the observer's /api/live through curl (the URL ota.rs already polls), caches it 2 s per window,
marks this machine's blocks as the lane 'you' by matching the miner id against every card's vote-key ids, and
answers {ok:false} when the observer is unreachable so the UI draws its own strip; four unit tests shape a fixture
in the observer's reply shape. The local node speaks gRPC and wRPC only, so a local-node source stays owed.
The copy sweep: no fleet, lane, identities, prior, hill climb, sweep, DAA, digest, manifest, shard, segment,
aggregator, verifier, worker or engine on a user surface; every toast and event line one sentence; the full list
in docs/plans/miner-ui-4-copy.md. The per-card goal's inherit option reads 'Default · Balanced' and follows the
shared goal; the cap's unpin is 'Back to Default'; the Cards strip is titled Ember Tune; each goal carries its
meaning as a tooltip. 37 UI tests pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
6.9 KiB
Igneum Miner UI 4: Overview, Cards, Ember on the row
6 October 2026, evening. the project lead: "add Cards under its own tab, and have Overview and the cool mining demo on the main
page with the revamped C option", then "incorporate Ember into the Cards tab beautifully", then "I would like the new
miner UI to go into .15". Branch miner-ui-4 from the 0.3.15 tip 6ea9497 (untouched), worktree
../igneum-wt-miner-ui-4. Rust: one route (GET /live-dag.js, server.rs), nothing else.
1. The rail
Overview, Cards, Earnings, Prove, Settings; Logs and Quit in the foot; a bottom tab bar under 720 px.
2. Overview
| Block | What |
|---|---|
| The hero (the project lead's pick, C) | the fleet rate at 84 px on a graphite-to-obsidian fade, W with MH/W, £ a day at the user's price, blocks (this run, lifetime) beside it; Start/Stop as the full-width bar under it with its state sentence at the right |
| The chain, live | the site's live-dag.js (a byte copy at app/igneum-app/ui/live-dag.js, served by the engine at /live-dag.js), mounted on the engine's own api/live with mine: 'you' so this machine's lane reads "you" and its blocks take a molten ring; the legend words in HTML under the canvas: yours, pending, included, excluded, proven, locked; Pause; a tooltip per block. The engine's /api/live is OWED (it maps the local node's RPC into the site's reply shape: {ok, state, blocks[{hash, ts, blue_score, daa, parents, chain, color, miner, locked, final, shards, proven}], finality.checkpoints}); until it exists the module reports failed and the app's own 10-minute blocks strip draws in its place with the note "The full chain view needs the next engine update". |
| The node line | as 0.3.15, Details under it |
| Activity | the event feed in the app's voice (the strip canvas moved into the chain card as the fallback) |
3. Cards
| Block | What |
|---|---|
| The Tuning strip | the goal control (Efficiency, Balanced, Maximum) with "about £X a day at Y W"; the fleet saving ("Ember saves 84 W, £0.56 a day over 1 tuned card", View.fleetSaving); Tune all; the schedule line; Power control only when an NVIDIA card has it off |
| The rows | as 0.3.15 (state word with its reason, MH/s, W with MH/W, £ a day, °C, the switch, the chevron) with Ember as the second layer |
| The flame mark | the brand mark's two polygons, 16 px, outline when not tuned, filling bottom-up with step k of n while tuning (molten), solid ember when tuned, half grey when measured only |
| The Ember line | "Tuned 2 h ago · 2470 MHz at 80% · next check Sunday · saves 84 W, 0.16% of rate"; "Tuning: step 4 of 9 · about 7 min left" with the bar; "Measured, not tuned · Power control is off"; "Not tuned yet · starts after 2 min of steady mining"; the fleet pause and a stop in the same place |
| The W cell | before → after when tuned (tune_before_watts → the live draw) |
| The MH/W cell | a 44 px sparkline of the ladder's MH/W per step, the chosen step as a dot |
| Tune / Retune / Stop | the row's button; Retune once tuned |
| The chevron | the cap slider, the applied or Retry line, memory and clocks, the card's own goal (As the fleet, Efficiency, Balanced, Maximum: POST api/tune/goal {key, goal}, "" clears; a build without it gets a toast), Retune, the Tuned line, the curve (W across, MH/s up, every step a dot, the chosen ringed, a rejected one hollow), identities, the facts |
Fields read (Ember Tune 5429e82, on the 0.3.16 lane): tune_before_watts, tune_before_mhs (step 0 of the last full
plan; 0 = never; the UI falls back to tune_curve[0] only for a full plan on an older build and claims no saving for a
confirm or baseline plan), sweep_watts, sweep_mhs, sweep_eff, tune_curve, tune_goal, tune_step,
tune_steps, tune_eta_s, sweep_state, sweep_note, tune_floor (asked for). View.ember and View.beforeOf
carry the rules; view.test.mjs covers every state.
4. Screenshots (the mock on port 4318 with the rig state, the 5090 tuned, the 4070 at step 4 of 9)
docs/plans/miner-ui-4/u01-overview-dark-1440.png, u02-overview-light-1440.png, u03-cards-dark-1440.png (the
5090's details open: cap, goal, Retune, the curve), u04-cards-light-1440.png, u05-overview-dark-390.png,
u06-cards-dark-390.png, u07-overview-light-390.png, u08-cards-light-390.png, u09-overview-mac-dark.png.
The chain scene in the shots is the mock's /api/live (four lanes, one block a second, "you" ringed); on a real
0.3.15 engine the fallback strip shows until /api/live lands.
5. Owed
| What | Who |
|---|---|
GET /api/live on the engine: the local node's blocks over the window, mapped into the site's shape, this machine's vote keys as the lane "you" |
engine |
card.tune_floor |
Ember (said yes) |
watts_total, pounds_per_day for the tray tip; address.balance_wei; price_gbp_per_ign |
engine (from miner-ui-3) |
Re-copy site/live-dag.js over app/igneum-app/ui/live-dag.js when the site agent's audit commit lands, so the two stay identical |
the shipper at merge |
Tests: node --test app/igneum-app/ui/*.test.mjs: 37 pass.
6. The second tip (20:0x UK): /api/live on the engine, the copy sweep, the project lead's goal rulings
| What | Where |
|---|---|
GET /api/live on the engine: the observer's /api/live (the URL ota.rs already polls for the identity count) through curl, cached 2 s per window, this machine's blocks marked as the lane "you" by matching the block's miner id (the first 8 hex of its vote key hash) against every card's ids; state.you_blocks and state.source: "observer" added; {ok:false, error} when the observer is unreachable or the build has no observer address, which the UI meets with its own blocks strip. Four unit tests on the shaping from a fixture in the observer's shape (live::tests). The local node speaks gRPC and wRPC only and the engine carries no client for either (no TLS, no websocket stack), so a local-node source is OWED (a JSON endpoint on igneumd, or a wRPC client here); the reply shape stays the same when it comes. |
src/live.rs, src/server.rs, src/main.rs, ota::live_api_from made pub |
The copy sweep: 150 strings, every one listed in docs/plans/miner-ui-4-copy.md |
ui/index.html, ui/app.js, the tests |
| the project lead's rulings: the per-card inherit option is "Default · Balanced" (grey, following the shared goal); "Default" wherever a per-card setting follows the shared one (the cap's "Back to Default"); the Cards strip is titled "Ember Tune"; every goal option carries its meaning as a tooltip (Efficiency "most hashes per watt, a little rate traded", Balanced "a little rate for most of the saving", Maximum "most hashes, watts second", Default "the goal set for all cards") | ui/index.html, ui/app.js |
The Rust tests did not run on this Mac (its cargo predates the lock file's version 4); the box runs them at the
merge, as the rule says. UI tests: 37 pass. Screenshots u01, u03, u04, u06 reshot after the sweep.