From d6f4c0c21e6f18c306ff954ff2c2b9fa15d96fd0 Mon Sep 17 00:00:00 2001 From: rob thijssen Date: Wed, 9 Sep 2026 17:15:29 +0300 Subject: [PATCH] ui: only advertise chains we can actually read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nav listed every chain telemetry names, with the ones we hold no RPC endpoint for as disabled chips saying "no endpoint". The reasoning was that they are part of the network and hiding them misrepresents its shape. In practice it put five dead chips above the standings on every page — four of which have never had anything behind them — and made the one live chain the smallest thing in the row. Nothing is blocked. `/:chain/...` still resolves for any chain the backend knows, `/v1/chains` still reports every one of them with its `tracking`, and promoting one is still a single `[[chains]]` entry. This is only about what the page offers unprompted. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5 --- crates/blackbeard-entities/src/chain.rs | 22 ++++--- web/src/api/generated/Tracking.ts | 18 +++--- web/src/components/ChainSwitcher.tsx | 76 ++++++++----------------- web/src/index.css | 13 ----- 4 files changed, 48 insertions(+), 81 deletions(-) diff --git a/crates/blackbeard-entities/src/chain.rs b/crates/blackbeard-entities/src/chain.rs index 944172c..2c21ee7 100644 --- a/crates/blackbeard-entities/src/chain.rs +++ b/crates/blackbeard-entities/src/chain.rs @@ -60,13 +60,17 @@ pub enum ChainStatus { /// How much of a chain this observer can actually see. /// -/// The distinction is not cosmetic and the UI must not hide it. Authorship -/// comes from the `pow_` digest in a block **header**, and headers come from a -/// node's JSON-RPC — telemetry publishes block hashes and heights but never -/// headers. So a chain the observer has no node for is genuinely observable -/// (population, height, block time, client mix) and genuinely *not* -/// leaderboard-able, and presenting the two identically would be a lie about -/// what the numbers mean. +/// The distinction is real, not cosmetic. Authorship comes from the `pow_` +/// digest in a block **header**, and headers come from a node's JSON-RPC — +/// telemetry publishes block hashes and heights but never headers. So a chain +/// the observer has no node for is genuinely observable (population, height, +/// block time, client mix) and genuinely *not* leaderboard-able, and a page +/// presenting the two identically would be a lie about what the numbers mean. +/// +/// This field is what keeps the two apart. Every chain is reported here +/// regardless, because the API's job is to say what it can see; whether an +/// untracked chain is *offered* to a reader is the frontend's call, and the +/// nav no longer advertises them. /// /// Promoting a chain from one to the other is a single `[[chains]]` entry /// naming an RPC endpoint — no code path differs between the first chain and @@ -79,8 +83,8 @@ pub enum Tracking { /// per-miner leaderboard. Navigable. Full, /// The chain exists and telemetry names it and counts its nodes, but this - /// observer has no endpoint to read headers from. Listed, not navigable — - /// an empty page would be worse than an honest "no endpoint". + /// observer has no endpoint to read headers from, so there is no + /// leaderboard to be had. Reported, but not offered as a link. NoEndpoint, } diff --git a/web/src/api/generated/Tracking.ts b/web/src/api/generated/Tracking.ts index fb1852f..6bdfc78 100644 --- a/web/src/api/generated/Tracking.ts +++ b/web/src/api/generated/Tracking.ts @@ -3,13 +3,17 @@ /** * How much of a chain this observer can actually see. * - * The distinction is not cosmetic and the UI must not hide it. Authorship - * comes from the `pow_` digest in a block **header**, and headers come from a - * node's JSON-RPC — telemetry publishes block hashes and heights but never - * headers. So a chain the observer has no node for is genuinely observable - * (population, height, block time, client mix) and genuinely *not* - * leaderboard-able, and presenting the two identically would be a lie about - * what the numbers mean. + * The distinction is real, not cosmetic. Authorship comes from the `pow_` + * digest in a block **header**, and headers come from a node's JSON-RPC — + * telemetry publishes block hashes and heights but never headers. So a chain + * the observer has no node for is genuinely observable (population, height, + * block time, client mix) and genuinely *not* leaderboard-able, and a page + * presenting the two identically would be a lie about what the numbers mean. + * + * This field is what keeps the two apart. Every chain is reported here + * regardless, because the API's job is to say what it can see; whether an + * untracked chain is *offered* to a reader is the frontend's call, and the + * nav no longer advertises them. * * Promoting a chain from one to the other is a single `[[chains]]` entry * naming an RPC endpoint — no code path differs between the first chain and diff --git a/web/src/components/ChainSwitcher.tsx b/web/src/components/ChainSwitcher.tsx index a95d825..26ab724 100644 --- a/web/src/components/ChainSwitcher.tsx +++ b/web/src/components/ChainSwitcher.tsx @@ -1,15 +1,18 @@ /** * The chain nav. * - * Every Quantus chain substrate-telemetry knows about, ordered by node count - * descending — the busiest chain is the one most people came for, and the order + * Only the chains this observer holds an RPC endpoint for, ordered by node + * count descending — the busiest is the one most people came for, and the order * is a real fact about the network rather than a config ordering. * - * Chains this observer holds no RPC endpoint for are **listed but not - * navigable**. That is deliberate: they are part of the network and hiding them - * would misrepresent its shape, but authorship comes from block headers and - * only a node serves those — so there is nothing behind the link. A disabled - * button that says why beats a navigable one that leads to an empty board. + * The rest are **not advertised**. They were listed as disabled chips for a + * while, on the reasoning that they are part of the network and hiding them + * misrepresents its shape. In practice that put five dead chips above the + * standings on every page, four of which had never had anything behind them, + * and the one live chain was the smallest thing in the row. Nothing is + * blocked: `/:chain/...` still resolves for any chain the backend knows, and + * the API still reports every one of them with its `tracking`. This is only + * about what the page offers unprompted. */ import { Link } from 'react-router-dom' @@ -19,9 +22,6 @@ import type { Window as WindowName } from '../api/generated/Window' import { href } from '../lib/routes' function reason(chain: ChainInfo): string { - if (chain.tracking === 'no_endpoint') { - return `${chain.display_name} has no public RPC endpoint this observer can reach, so its blocks' authors cannot be read. It is listed because it is part of the network.` - } switch (chain.status) { case 'awaiting': return `${chain.display_name} has not produced a block yet.` @@ -44,61 +44,33 @@ export function ChainSwitcher({ /** Carried across the switch: changing chain is not a reason to change span. */ window: WindowName }) { - if (chains.length === 0) return null + const navigable = chains.filter((chain) => chain.tracking === 'full') + if (navigable.length === 0) return null return (