igneum/docs/plans/miner-ui-4.md
igneum-labs f4ddfe5b0a miner-ui-4: carry the clock sample's exec-record gate (0f6b4650, ledger N7); version words follow the 0.3.19 renumbering
Only update.rs from 0f6b4650; its version bumps stay with the shipper's cut.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 032e1f8717)
2026-10-07 08:33:27 +00:00

17 KiB
Raw Blame History

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.

7. Pinned to the next engine (ember-tune fd03f35, then b55bc1f), 20:5x UK

Numbering (the shipper, 21:3x UK): 0.3.16 is tonight's corrective cut, the same release as 0.3.15 (this branch's ui/ at 872f1c9) under a new number; the engine fields and the pinning below ship as 0.3.17 with miner-ui-4's tip e31acab.

Field Where the UI reads it
mining.watts_total, mining.pounds_per_day the Overview hero and Earnings use the engine's figures when present (View.fleetWatts, View.fleetPounds), else the UI's own sums as before
address.balance_wei (string or null), address.balance_age_s (-1 until read), address.balance_note, address.price_gbp_per_ign (null until a signed manifest carries a price) Earnings' first row: "12.3456 IGN · in the wallet · read 20 s ago" (View.balanceWords); a priced balance shows £ with "at the signed price"; an older engine without the field keeps the row as it was
card.tune_floor the Ember line's "(the lowest clock Ember will set)"
/api/live reply source ("node" when igneumd answers igneum_getRecentBlocks, "site" until then; pending: true before the first body) the chain scene's note (View.liveNote): "From your node. Your blocks are ringed." or "From Igneum’s observer. Your blocks are ringed."; "you" is matched in the UI from every card's vote-key ids whatever the source names the lane

At the 0.3.17 merge src/live.rs is ember-tune's (the fuller one, node first, site second); this branch's 0.3.15 file is superseded and its source word is "site" too, so nothing in the UI changes between the two.

8. N4 (the frozen tip), the capture route, and PC 2's light render (6 October 2026, 23:0x to 23:4x UK)

What Where
N4, the app half: sync_decision_v2 (the old rule AND the watch line's tip_age_s at or under 120 s AND peers >= 1; the cause word frozen / no peers / syncing / behind on node.sync_cause; the state word "behind" or "no peers", never "synced" on a frozen tip); is_stall_exit (code 45 or a "STALLED '" tail line; the first restarts the miner, the second since the node started restarts the node and re-dials its peers); node.tip_age_s, node.stall_exits. The known-failed case is the first test. UI: "Node behind · 1 peer · 133,000 blocks · last block 53 min ago" with the cause line, the pill "Node behind", the big button's "waiting: the node is behind the chain". The node lane's miner half is ca3-v4-0316. src/engine.rs, src/state.rs, ui/app.js, view.test.mjs
(b) a screenshot from the machine itself: POST /api/shot {name} writes SHOT <path> on the host protocol (beside URL, STATE, EXIT); the Mac host answers with WKWebView.takeSnapshot, the Windows host with WebView2 CapturePreview (PNG), to <data root>/shots/igneum-<name>-<unix>.png (live::shot_path, the name reduced to [a-z0-9-]); relay/playbooks/app-shot.ps1 asks and waits, a collect job's glob shots/*.png brings the file back. Read-only. The Swift parses (swiftc -parse); the C++ builds on the GitHub runner at the cut (BUILD-APP.bat already links shlwapi.lib). src/server.rs, src/live.rs, app/mac/IgneumMiner.swift, app/windows/host.cpp, relay/playbooks/app-shot.ps1
The light-theme showcase capture: Edge headless on PC 2 wrote nothing in three takes (154.0.4258.53, exit 0, no stderr, in the app's elevated session); PC 2's api/state and api/live were fetched by relay/playbooks/app-state-grab.ps1 into the data root and collected, and the ui-mock replayed them (IGNEUM_MOCK_STATE, IGNEUM_MOCK_LIVE) through the real 0.3.16 UI on the Mac: docs/plans/miner-ui-4/pc2-0.3.16/pc2-overview-light-1440.png, pc2-cards-light-1440.png, pc2-overview-dark-1440.png, captioned "rendered from PC 2's live state at 23:33 UK" (DESKTOP-KMCV30N, RTX 5090 at 103 MH/s and 317 W, node synced, 5 peers). Handed to the site lane. tools/ui-mock/server.mjs, relay/playbooks/app-state-grab.ps1
The fleet watts count mining and tuning cards only (PC 2's idle integrated card drew 48 W and was summed); the Ember off-state line no longer promises a start. ui/app.js

Tests: UI 39; the app crate on the box 152 passed (the N4 pair, the shot path, the four live tests among them).

9. The rebased fold (7 October 2026, 23:20 UK; renumbered: tonight's 0.3.17 is a node-only hotfix on the 0.3.16 app tree, and this branch at afc331bd ships as 0.3.18 on release-0.3.18)

  • Rebased onto a9eb58f1 (origin/release-0.3.15). ui/ comes wholly from this branch; Ember's finality-paused hunk (ember-tune 1357d280) is folded in as View.finalityWords(f): while finality is paused the node sub-line and the lock row say "paused" and nothing calls a lock final. Test in view.test.mjs (41 UI tests).
  • src/extnode.rs reads the node lane's igneum_getNodeInfo shape: params (every resolved consensus field, compared value by value against the manifest's keys), network (must be equal), powEngine ("stub" = refused on the port check, and a fault on the app's own node: node state stub, pill "Node fault", one error event). 168 box tests.
  • src/live.rs is Ember's merged module at the merge (nothing of this branch's lives there any more).
  • External node re-decided (the Mac, 7 October 2026 00:18 UK: the hand node left and the app sat on "node stopped" all night). While the app reads another node, or refused one, it checks the port every 5 s; gone for 60 s (extnode::TAKEOVER_WAIT_S) it starts its own node on the freed ports and says so in Activity; a node back inside the wait is kept. api/state node.mode own | external | none with node.mode_reason, shown under the Node details. Test an_external_node_that_goes_away_hands_the_ports_to_the_app_after_the_wait (169 box tests).

10. The merge check (7 October 2026, 03:20 UK; 74c12665, on the 0.3.18 list after afc331bd)

  • Design note from the node lane's 30-s propagation reading: a node behind a slow link keeps mining its own chain and the N4 "synced" test cannot see it. New src/merge.rs (pure): not_merging(MergeCheck) is true when every sink the network reports is more than the merge depth ahead of ours (the node's params.merge_depth via igneum_getNodeInfo, else 3,600) and none of our blocks has appeared in the network's view for 120 s, after at least 120 s of mining. Known-failed test first (a_node_whose_blocks_never_merge_reads_behind_and_holds_the_miner).
  • Engine: every 30 s while mining, the observer's /api/live (600-s window, this machine's lanes marked) gives the network's sink and the newest block of ours it shows (merge::view_of); the watch line's decision runs after sync_decision_v2; the state reads "behind" with sync_cause "not merging" and the message "behind: your blocks are not merging into the network"; the miner is held through the existing synced gate; one error event with the gap in blocks. Source owed: peers' own sinks (igneum_getNodeInfo over the peer list) when the node exposes them; the observer is the first view.
  • UI: nodeWords' behind case names the cause. 42 UI tests, 172 box tests. Nothing of this on the Mac tonight.
  • Decided (main, 7 October 2026, 05:00 UK): igneum_getPeers stays on 0.3.19; the observer-based merge check ships in 0.3.18. The Mac check when it takes 0.3.18: the hands moved to the build box at 00:18 UK, so hand node 1 no longer holds 26610; the app, left in external mode, reads "node stopped" and starts its own node only on relaunch (the apply). Check after the apply: own node on 26610/26611, api/state node.mode own, digest_source rpc, synced; the miner stays paused. Read-only; nothing else that night.
  • Node side landed: 6e4ace3f on build/ca3-v4-0318 (04:53 UK) adds blockrate: { bps, ghostdagK, mergeDepth, finalityDepth, pruningDepth } to igneum_getNodeInfo (devnet: bps 1, ghostdagK 18, mergeDepth 3600). The app reads blockrate.mergeDepth (b7d33e8e) and keeps the 3,600 fallback, since whether 6e4ace3f rides in 0.3.18 is the shipper's call. igneum_getPeers (lastDeliveredBlueScore) stays on 0.3.19.

11. Workers after an OTA relaunch (7 October 2026, 05:20 UK; 0.3.18)

  • Known-failed case: PC 1 at the 0.3.17 relaunch (04:51:43Z, docs/plans/release-0.3.17.md PC 1 row): the app started its workers while the node was still syncing; the card watchdog clocked the silence anyway (a miner with no template prints no status), restarted each once, then faulted every card "no status line from the miner for 90 s (restarted once already)", pid 0, 0.0 MH/s on; faulted cards were never restarted, including at the synced transition.
  • Fix (src/watchdog.rs, src/engine.rs): a miner is judged only while the node is synced (the start, no-status and zero-rate clocks hold at now while it is not); a worker silent from its start with the node synced is one restart then a fault at 60 s (START_S), 90 s stays for a running one; a fault the node's readiness explains (faulted_by_node) is released by Event::NodeSynced when the node syncs, the card starts again with an Activity line; one Activity line per card at its first worker start. Test a_worker_started_while_the_node_syncs_is_not_faulted_and_a_faulted_one_returns_at_sync (recorded state), 173 box tests.
  • The pre-apply card set already persists (cards' enabled flags live in settings and the relaunch planned every enabled card); the fault, not the plan, was the failure.
  • Read-back changed the reading (relaunch-engine-and-workers.txt, 05:40 UK): PC 1's node reported synced=true from the first second; the miners started at +14, +25 and +38 s, subscribed, then every template fetch timed out at 5 s for 180 s while the node ran its finality catch-up (checkpoints 8053 to 8055 locking), so no status line was printed; the watchdog restarted each at 90 s and faulted each at 180 s. Rule added: "template fetch timed out" is the miner's heartbeat (Event::TemplateTimeout): the silence clocks start over from the line, the card reads "waiting for the node to answer block templates", one Activity line per episode, and the miner is judged again from its last line (60 s of nothing at all after it is still a restart). The synced-gate half stays. Test extended with the recorded sequence; 173 box tests. The node-side half (the template RPC blocking under the finality replay after a restart) is with the node lane.
  • Renumbered again (main, 7 October 2026, 09:2x UK): 0.3.18 is an app-only cut on the 0.3.17 tree (the clock sample gated on igneum_getExecStatus, ledger N7, commit 0e6c1248); this tree ships as 0.3.19 (release-0.3.19, node release-0.3.19-node); decimals is 0.3.20. 0e6c1248's update.rs hunk is carried here so the branch is whole; the Mac check comes on the 0.3.19 apply.