Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 0cb2d9a676)
14 KiB
Igneum Miner UI 5: the ladder shown everywhere, the miner's first month
7 October 2026, 10:1x to 11:xx UK. Mission item 6 (docs/analysis/mission/mission.md 2.6; the brief is
docs/analysis/mission/reinvent.md 1, 2, 3.2 to 3.7 and 7). Branch miner-ui-5 from the miner-ui-4 tip 5d9c55a2
(release-0.3.18, since renumbered 0.3.19), worktree ../igneum-wt-miner-ui-5. Nothing published; the shipper cuts it.
The site lane (a4b202cabca2d95c0) owns /live and /miners; the node lane (a283f5f0d364ceef0) owns the RPC fields
listed in section 6.
1. What is built
| Part | Where | What |
|---|---|---|
| (a) The Poisson count-up | Overview, #first-wait; View.poisson |
from the first job until the first block of this install: "A card like yours finds a block about every 1.6 hours on today's network. Chance so far: 31%." The gap is /api/stats hashrate over the fleet rate (mining.hash_total) at one block a second; the chance is 1 - e^(-elapsed / gap) from the moment mining started this run. Over an 8-hour gap the pool line sits under it. The ring fills with the chance. Hover names the two fields and the formula. |
| (b) The block card | Overview, #block-card; View.blockCard; src/ladder.rs |
raised by the engine on the first block of the install, every 100th, 1,000th and 10,000th, and the first block of a new card (ladder.milestone); in place, never a dialog; stays until dismissed (the key is stored, so a relaunch does not re-raise it). "Block 1,284,117 is yours." (the DAA score the node logged), the card and its rate and share, "4.88 IGN to 0xdd44…86E8 (72 producer points, 8 for signing, ramp 19%)", "rank 41 of 1,204 keys · day 23 of 30", the hash, Open in the explorer (/block/<hash>), Save the card, Copy the link. The hash joins the miner's ACCEPTED block nonce= line to the node's PoW accepted <hash> … nonce line by nonce (the node logs every block it validates). |
| Save the card | drawCard in app.js, POST /api/card, src/card.rs, platform::reveal_file |
a 1200x630 PNG drawn on a canvas from the card's own words with the fonts the app ships (no network call): the mark, "Block N is yours", the card and rate, the IGN line, rank and day, the miner key and hash, the explorer URL. The miner's object, never the project's number: no network rate, no block count. The engine writes it under <data root>/cards/ and reveals it in Finder or Explorer. u00-block-card-1200x630.png is the one the mock drew. |
| (c) Earnings in IGN first | Earnings, the money card; View.earningsLines |
line 1 "7,680 IGN a day at your last hour's rate (4 blocks, 80.00 IGN each) · about 6,912 IGN expected at 100 MH/s" (measured first, the expectation beside it; the expectation alone before the first block); line 2 "2,592 blocks in 30 days, 207,360 IGN" from getFinalityWeights keys[].blocks at today's reward, with the shards; line 3 the balance (unchanged); line 4 the typed price: a £ per IGN the user types, kept in the page's storage, never fetched, "about £384 a day at £0.05 per IGN, your figure · no exchange lists IGN"; line 5 "your cards are 0.100% of the network · weight rank 41 of 1,204 keys · 23 of 30 days"; line 6 electricity unchanged plus "2,133 IGN per kWh"; the dev fee unchanged. Every number's title names its field. |
| The per-card line | Cards rows | an "IGN a day" cell per card from its rate, the network rate and the reward (hover: the fields) |
| (d) The ladder | Earnings #ladder-card (the rungs with their lines and hovers), Overview #ladder-strip (the seven chips and the current rung's line); View.ladderRungs |
rungs: first block; your key has a vote; your signature is in checkpoint K; full window; rank by weight; signing streak; your card proved a shard. Every threshold is read from the node's params (dust, weightWindow, presenceWindow), never a constant: the devnet reads "5 blocks to a vote" and "2 of 2 hours", mainnet "100 blocks" and "30 of 30 days". The words, in copy law: "Your key has a vote." "Your signature is in checkpoint 184,220." "30 of 30 days. Full weight." "Rank 41 of 1,204 keys." "Signing 8 of 80 points, 41 days unbroken." "Your card proved shard 3 of block 1,284,117." |
| The chain facts | GET /api/ladder, src/chainfacts.rs |
the node's igneum_getFinalityWeights on the EVM port first (OWED on the node, section 6), else the observer's /api/live finality.weights (the same reply relayed, keys cut to 64, ids as 8 hex) plus /api/stats; this machine's keys ranked by blocks; cached 10 s; the reply names its source and the dashboard says it ("through the observer" until the node answers). |
| The machine's record | src/ladder.rs, <app dir>/ladder.json, api/state.ladder |
first block (time, card, hash, DAA), blocks per UTC day (the days-mined ring), the checkpoints signed and which locked (VOTE index= and LOCK checkpoint lines), the signing streak (a gap over the presence window ends it), the paid shards (src/prover.rs), the milestone, the first-hour marks (node synced, card mining). |
| (e) The public profile opt-in | Settings "Make my page public"; settings.profile_public, api/settings {profile_public} |
off by default; the words say what becomes public (key ids, blocks, rank, presence, card model). The site reads the switch through the app's existing report path (owed to the site lane; today the switch is stored and shown, nothing leaves the machine). |
| (f) The first-hour timeline | Earnings "Your first hour"; View.timeline |
Installed (settings.installed_at), App started, Node synced (ladder.first_synced_at), Card mining (ladder.first_mining_at), First block, First payout (the coinbase of the first block), each as "+N min" from the install. |
| Prove | #shard-card |
"Your card proved shard 3 of block 1,284,117." with the IGN, on the newest paid record |
| Activity | EVENT_MAP |
"Your first block, found by RTX 4070", "Block 1,000 of this machine, found by …", "First block on …" |
2. The chain scene (EMBER 02, taken from the site lane)
the project lead's renderer pack (11:3x UK): ui/live-dag.js (sha256 82d56565…, IgneumDag 2.0.2, master 9162c847: the phone-width options narrow/narrowWindow 30/narrowLanes 4/narrowMinNode 11, hairlines off when narrow, percent-only checkpoint labels; the app passes fps 60 on both mounts and narrow when the window is under 720 px on the compact card, since compact never auto-narrows; 2.0.1 e78f2d38… added the site lane's real-data options, maxLanes 7 with the own key first and an OTHERS lane, minNode 9.5, merge hairlines within two lanes, provenGlow; 2.0.0 was 582f2e73…) and ui/proof-core.js (c2a094a6…,
IgneumProof 2.0.0) are byte-identical copies of the site-ui-4 files; the engine serves proof-core.js beside
live-dag.js. The Overview card mounts the compact mode (compact: true, window: 60, poll: false); Inspect, or a
click on a block, opens the full scene under it with the block inspector in the site's words (Proof in progress /
Proof complete / Checkpoint locked / Block observed; Proof shards, Inclusion, Miner key, Blue score, DAA score,
Parent links, Header time, Checkpoint, Finality; the note on separate states), the filter tabs (All blocks, Selected
chain, Your blocks), the window stepper, Follow and Pause. One owner of the feed: the page reads the engine's
api/live?window=120 every 2 s and pushes the reply into both scenes; mine matches every vote key id of this
machine's cards (the 8 hex the feed names as b.miner), never the payout address. --included and --excluded are on
:root for both themes. The fallback strip (this machine's own blocks) draws after three failed reads. The legend
reads "Your blocks" as in the reference.
Caption fact from the site lane: the observer's per-block shard states read "planned" for every chain block in the window while its paid counters move (10:08 UK), so the live scene shows no proven block until the observer's read is fixed (the fleet lane has it); the mock's feed shows the proven state.
3. Gate results
| Gate | Result |
|---|---|
| A Devnet 2 key walks every rung on screen in a view test | view.test.mjs "a Devnet 2 key walks every rung on screen": key 6ad0e117 under the finality params the devnet node reports (api/live params, 7 Oct 2026: dust 5, weightWindow 7,200, presenceWindow 20; Devnet 2's override file sets none of them, so it runs the same values) goes nothing → first block → vote → signature in checkpoint 8,496 → 2 of 2 hours → rank 3 of 4 → 15 min unbroken → shard 3 of block 1,284,117; every rung's field matches `getFinalityWeights |
| Every number names its RPC field on hover | View.FIELDS (23 sentences) in every title; the rungs, the Earnings lines, the block card lines, the Cards cell, the count-up |
| The PNG opens; no network call is made to make it | u00-block-card-1200x630.png drawn by the mock page through Playwright's chromium; the page's only calls while drawing are document.fonts.load of the app's own woff2 files |
| "First share within 10 minutes of download in 9 of 10 fresh Windows installs" | written as a runbook for the shipper, not run: docs/plans/miner-ui-5-first-share-runbook.md |
| Tests | UI 47 (42 before; the five new ones); the app crate on the box 190 + 27 + 8 (ladder 10, chainfacts 4, card 3 among them) |
4. Captures (docs/plans/miner-ui-5-shots/, the ui-mock's scenarios firstwait, firstblock, ladder)
u00-block-card-1200x630.png the saved card; u01-overview-firstwait-dark-1440 the count-up and the strip; u02/u03
the first block's card, dark and light; u04/u14 the full scene with a block in the inspector, dark and light;
u05/u06 Earnings with the ladder and the timeline open; u07 the shard card; u08 the Cards row with IGN a day;
u09 Settings with the profile switch; u10 to u13 the Overview and Earnings at 390, both themes. Captured through
Playwright's own chromium (never Google Chrome.app) against tools/ui-mock/server.mjs 4319.
5. Owed by this lane, and what the site lane and the Discord lane take
| What | Who |
|---|---|
| The Cards setup screen's "about N blocks a day" from the priors file (reinvent 3.2, Cards rows) | this lane, once the priors ship in the signed manifest (the file lives on the site today) |
| The QR of the explorer link on the saved card (reinvent 3.3) | this lane; the URL is printed in full instead |
| The opt-in reaching the site (the address page's noindex lifted for a key whose app switched it on) | the site lane and this lane together: the switch rides the app's existing report upload as profile_public |
/live weight leaderboard and /miners census with the same words |
the site lane; the field list and the rung words were sent to it (section 7) |
#first-blocks posts and the Voter and Window roles |
the Discord lane; the shapes are in docs/community/discord/ladder.md, nothing posted |
| Sound on a block | not built (verdict table: watch) |
6. RPC fields the node does not expose yet (sent to the node lane)
| Field | Why the app needs it | What the app does meanwhile |
|---|---|---|
igneum_getFinalityWeights on the EVM JSON-RPC port (the wRPC getFinalityWeights reply as is) |
the engine carries no wRPC client; today the facts come through the observer's relay, which cuts the table to 64 keys and so cannot rank a key below 64 | reads /api/live finality.weights and says "through the observer" on hover; "Below the top 64 keys" when the key is not listed |
igneum_getFinalityCheckpoints the same way |
the locked index and the certificate list from this machine's node | /api/stats finality.latest_locked_index |
getFinalityCheckpoints checkpoints[].signers (the key hashes in the certificate bitmap), or a per-key lastSignedLockedIndex |
"Your signature is in checkpoint K" as a chain fact | the app's own VOTE index= lines, read as locked once a LOCK checkpoint line reaches them; the hover says so |
getFinalityWeights keys[].daysMined (distinct days with a blue block in the window) and keys[].firstBlockDaa |
"30 of 30 days" as a chain fact; the Window role; the /live days-mined column |
the app's own day ring (ladder.json), which only counts what this install saw |
getFinalityWeights keys[].streakDays or keys[].lastMissedDaa |
the signing streak as a chain fact | the app's own unbroken-vote reading; participation is shown beside it |
getFinalityWeights keys[].rank or a getFinalityKey {keyHash} reply with blocks, voter, participation, rank, daysMined, streak |
one call per machine instead of the whole table | the table sorted on the client |
igneum_getProofRecords by key (the paid shards of a key over a range) |
"your card proved shard N of block M" for records this install did not watch | src/prover.rs reports what it submitted and saw paid |
7. Sent to the site lane (so /live and /miners match the app word for word)
The rung names and lines of section 1 (d); the fields: getFinalityWeights params.dust / weightWindow / presenceWindow,
keys[].blocks / voter / participation / strippedUntilDaa / pubkey, totalWeight, activeWeight, voters,
checkpointIndex; getFinalityCheckpoints latestLockedIndex / nextIndex / finalityActive; /api/stats hashrate,
block_reward.miner_ign, block_reward.ramp_factor; the leaderboard column order (rank, key id, blocks in window,
days mined, presence, last block) with "weight, never hashrate: a card that arrived today sits at the bottom".
8. Copy added (the copy law: no em dashes, no two-beat antithesis, no aphorisms; every line names a number or an action)
"Waiting for your first block." "A card like yours finds a block about every 1.6 hours on today's network. Chance so far: 31%." "Above an 8-hour gap a pool pays by the share, every minute. The pool setting arrives with pool-0." "Block 1,284,117 is yours." "Open in the explorer" "Save the card" "Copy the link" "Your key has a vote." "61 of 100 blocks to a vote." "Your signature is in checkpoint 184,220." "30 of 30 days. Full weight." "Rank 41 of 1,204 keys." "Below the top 64 keys." "Signing 8 of 80 points, 41 days unbroken." "Your card proved shard 3 of block 1,284,117." "Proving is off." "Type a price" "Use it" "Make my page public" "Your first hour" "The ladder".