From f3b43179de2db05a047ce251fe0bc6449f9a0f4b Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 19:52:16 +0000 Subject: [PATCH] Ember /api/live: each block and miner row carries "miner", the lane id = the first 8 hex of the vote key hash, the same 8 a card's ids carry (the UI marks "you" by it); test Co-Authored-By: Claude Fable 5.1 --- app/igneum-app/src/live.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/app/igneum-app/src/live.rs b/app/igneum-app/src/live.rs index ba703a2ec..2593a05c1 100644 --- a/app/igneum-app/src/live.rs +++ b/app/igneum-app/src/live.rs @@ -74,24 +74,26 @@ pub fn shape_from_blocks(blocks: &[RecentBlock], now_ms: u64, window_s: u64) -> let rows: Vec = blocks .iter() .filter(|b| b.timestamp_ms >= win) - .map(|b| json!({ "hash": short(&b.hash), "blue_score": b.blue_score, "daa": b.daa_score, "timestamp_ms": b.timestamp_ms, "is_chain_block": b.is_chain_block, "color": if b.color.is_empty() { "pending" } else { b.color.as_str() }, "vote_key_hash": short(&b.vote_key_hash), "timestamp_source": if b.timestamp_source.is_empty() { "header" } else { b.timestamp_source.as_str() } })) + .map(|b| json!({ "hash": short(&b.hash), "blue_score": b.blue_score, "daa": b.daa_score, "timestamp_ms": b.timestamp_ms, "is_chain_block": b.is_chain_block, "color": if b.color.is_empty() { "pending" } else { b.color.as_str() }, "vote_key_hash": short(&b.vote_key_hash), "miner": lane(&b.vote_key_hash), "timestamp_source": if b.timestamp_source.is_empty() { "header" } else { b.timestamp_source.as_str() } })) .collect(); json!({ "miners_10m": miners.len(), "blocks_10m": recent.len(), "blocks_per_minute": per_min, "blocks": rows, - "miners": miners.iter().map(|(k, n)| json!({ "vote_key_hash": short(k), "blocks_10m": n })).collect::>(), + "miners": miners.iter().map(|(k, n)| json!({ "id": lane(k), "vote_key_hash": short(k), "blocks_10m": n })).collect::>(), }) } +/// The lane id the UI matches against a card's `ids`: the first 8 hex of the vote key hash (engine.rs gives a card +/// its ids the same way, `h.chars().take(8)`); "" when the block carries no key. +pub fn lane(vote_key_hash: &str) -> String { + vote_key_hash.trim_start_matches("0x").chars().take(8).collect() +} + +/// The site's `short`: the first 16 hex (site/api/live.mjs), so a hash reads the same from either source. fn short(h: &str) -> String { - let h = h.trim_start_matches("0x"); - if h.len() > 12 { - format!("{}..{}", &h[..6], &h[h.len() - 6..]) - } else { - h.to_string() - } + h.trim_start_matches("0x").chars().take(16).collect() } /// The reply for GET /api/live: cached when fresh, else a refresh is started and the last body (or `pending`) @@ -212,6 +214,10 @@ mod tests { assert_eq!(v["blocks"][0]["color"], "blue"); assert_eq!(v["blocks"][0]["timestamp_source"], "header"); assert_eq!(v["miners"].as_array().unwrap().len(), 3); + assert_eq!(v["blocks"][0]["miner"], "aa", "the lane id is the first 8 hex of the vote key hash, as a card's ids"); + assert_eq!(v["miners"][0]["id"], "aa", "the miners rows carry id, as the site's"); + assert_eq!(lane("0x0123456789abcdef"), "01234567"); + assert_eq!(lane(""), ""); } #[test] @@ -222,6 +228,6 @@ mod tests { assert_eq!((p[1].color.as_str(), p[1].timestamp_source.as_str()), ("red", "merging_block")); assert_eq!((p[0].blue_score, p[0].is_chain_block, p[0].color.as_str()), (5, true, "blue")); assert!(parse_recent(&json!(null)).is_empty()); - assert_eq!(short("0x1234567890abcdef"), "123456..abcdef"); + assert_eq!(short("0x1234567890abcdef00"), "1234567890abcdef", "the site's 16-hex short"); } }