the validators page counts every network, not just this one

Zoo and Hanzo run as L2s on the primary network, Pars and Osage as their own
L1s, so "how many validators secure Lux" is a question about all of them. The
Validators page answered with this chain's 6 while the landing page said 21 —
same word, two quantities, one site, and no way for a reader to tell which
question either was answering.

Both numbers were right. The page now shows every network with its own count
beside the total, and says how many answered so a partial sum is never read as
the whole. A network that cannot be read is still listed: dropping it would make
it look like a network with no validators.

The stake beside the landing-page total names its network now. Stake stays
primary-only — each network bonds its own currency and the sum of two currencies
is not a quantity — so leaving it unlabelled next to an all-network validator
count invited reading the two as one ratio.
This commit is contained in:
2026-08-10 21:31:16 -07:00
parent 5d1038aa06
commit e47a1993c4
2 changed files with 68 additions and 5 deletions
+7 -5
View File
@@ -262,10 +262,12 @@ const NetworkOverview = () => {
'py-4 px-4 gap-x-6 gap-y-3 border border-[var(--color-border-divider)]',
'bg-[var(--color-stats-bg)]',
) }>
{ /* Every L1 in the registry, not just this chain. The breakdown is
in the Validators card below. Stake stays primary-network only:
each chain bonds its own currency and the sum of two currencies
is not a quantity. */ }
{ /* Every network's validators, which is the number a reader wants:
the fleet securing Lux is not just the primary network's share
of it. Stake stays primary-only beside it because each network
bonds its own currency and the sum of two currencies is not a
quantity \u2014 so the label says which network the stake is for
rather than letting the two be read as one ratio. */ }
<Metric
label={ allValidators.answeredCount === allValidators.queriedCount ? 'Validators' : 'Validators (partial)' }
value={ allValidators.isKnown ? String(allValidators.total) : '\u2014' }
@@ -273,7 +275,7 @@ const NetworkOverview = () => {
/>
<div className="w-px h-7 hidden md:block bg-[var(--color-border-divider)]"/>
<Metric
label="Staked"
label={ `Staked on ${ config.chain.name || 'primary' }` }
value={ hasValidatorData ?
`${ formatStake(stats.totalStake) } ${ config.chain.currency.symbol || 'LUX' }` :
'\u2014' }
+61
View File
@@ -3,6 +3,8 @@ import React from 'react';
import config from 'configs/app';
import type { PChainValidator, ValidatorStats } from 'lib/api/pchain';
import { useNetworkValidators } from 'lib/api/pchain/useNetworkValidators';
import { cn } from 'lib/utils/cn';
import { formatStake, truncateNodeId } from './utils';
@@ -20,6 +22,62 @@ const UNKNOWN = '\u2014';
const TOP_VALIDATORS_COUNT = 20;
const PERCENTAGE_SCALE = 100;
// ---------------------------------------------------------------------------
// Every network's validators
// ---------------------------------------------------------------------------
// The cards above count THIS chain. Zoo and Hanzo run as L2s on the primary
// network and Pars and Osage as their own L1s, so a reader who wants to know
// how much iron is behind Lux needs all of them — a page titled "Validators"
// that shows one network's share is answering a narrower question than the one
// being asked.
//
// Every network is listed, including ones that did not answer. A chain dropped
// from the list would read as a chain with no validators, and the total says
// how many answered so a partial sum is never mistaken for the whole.
const NetworkValidators = () => {
const { networks, total, isKnown, isLoading, answeredCount, queriedCount } = useNetworkValidators();
if (networks.length <= 1) {
return null;
}
return (
<div className="rounded-lg border border-[var(--color-border-divider)] p-4">
<div className="flex items-baseline justify-between mb-3">
<span className="text-sm font-medium">All networks</span>
<Skeleton loading={ isLoading }>
<span className="font-mono text-sm">
{ isKnown ? total.toLocaleString() : UNKNOWN }
{ answeredCount !== queriedCount && (
<span className="ml-2 text-2xs text-[var(--color-text-secondary)]">
{ answeredCount } of { queriedCount } answered
</span>
) }
</span>
</Skeleton>
</div>
<div className="flex flex-col gap-1">
{ networks.map((network) => (
<div key={ network.chainId } className="flex items-center justify-between text-xs">
<span className="text-[var(--color-text-secondary)]">{ network.name }</span>
<Skeleton loading={ network.isLoading }>
<span className={ cn(
'font-mono',
network.status === 'live' ?
'text-[var(--color-text-primary)]' :
'text-[var(--color-text-secondary)]',
) }>
{ network.status === 'live' ? network.validatorCount : network.status }
</span>
</Skeleton>
</div>
)) }
</div>
</div>
);
};
// ---------------------------------------------------------------------------
// Stat card
// ---------------------------------------------------------------------------
@@ -201,6 +259,9 @@ const ValidatorsDashboard = ({ validators, stats, isLoading, isKnown }: Validato
/>
</div>
{ /* Every network, not just this one */ }
<NetworkValidators/>
{ /* Stake breakdown */ }
<StakeBreakdown stats={ stats } isLoading={ isLoading } isKnown={ isKnown }/>