ui: the block route is a page too

Same reasoning as the miner and account routes one commit ago, which the block
route was deliberately left out of: a block is read against the standings, and
the ticker row below is what you clicked to get here. On reflection that is an
argument about how someone arrived, not about what the URL names — and the URL
names one block. The board is what `/:chain/:window` is for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
This commit is contained in:
2026-09-09 17:44:58 +03:00
parent 1410d3dda7
commit 1260cc6d64
2 changed files with 28 additions and 31 deletions

View File

@@ -16,6 +16,7 @@ import { BlockTicker } from './components/BlockTicker'
import { ChainSwitcher } from './components/ChainSwitcher'
import { Leaderboard } from './components/Leaderboard'
import { MinerPanel } from './components/MinerPanel'
import { RuntimePanel } from './components/RuntimePanel'
import { StatBar } from './components/StatBar'
import { seconds, windowSpan } from './lib/format'
import { href, parse, WINDOWS } from './lib/routes'
@@ -67,12 +68,6 @@ export default function App() {
[chain, navigate],
)
/** Back to the standings, keeping the window the panel was opened from. */
const closePanel = useCallback(
() => navigate(href({ chain, window: route.window })),
[chain, route.window, navigate],
)
const interval =
state.summary?.block_interval_seconds ?? state.summary?.target_block_time_seconds ?? 6
const activeWindow = WINDOWS.find((w) => w.id === route.window) ?? WINDOWS[1]!
@@ -143,7 +138,6 @@ export default function App() {
block={route.block}
targetSeconds={info?.target_block_time_seconds ?? 6}
onResolved={blockResolved}
onClose={closePanel}
/>
)}
@@ -163,13 +157,14 @@ export default function App() {
<MinerPanel chain={chain} miner={route.miner} windowName={route.window} />
)}
{/* A miner and an account are pages, not things opened over the board.
Each is somebody's own record and the whole subject of the URL that
reached it; leaving eleven other miners' rows underneath is the site
failing to notice which page it is on. A block keeps the board,
because a block is read *against* it — who won this one, and where do
they sit — and the ticker below is the row you clicked to get here. */}
{!route.account && !route.miner && (
{/* Every one of these is a page, not something opened over the board. A
URL that names one thing has that thing as its whole subject, and
leaving eleven unrelated mining rows underneath is the site failing to
notice which page it is on. The board is what `/:chain/:window` is
for. */}
{route.runtime !== null && chain && <RuntimePanel chain={chain} spec={route.runtime} />}
{!route.account && !route.miner && !route.block && route.runtime === null && (
<div className="two-col">
<section className="panel">
<div className="panel-head">
@@ -215,6 +210,22 @@ export default function App() {
is the only identity the chain itself asserts. An <code>elsewhere</code> tag means the
name was earned by the same reward preimage on another chain, not observed on this one.
</span>
{state.summary?.spec_version !== null && state.summary?.spec_version !== undefined && (
<span>
{/* "Decoded how?" has an answer, and it is a page. Every event and
reward on this site was read against this runtime's own type
registry rather than against anything written here, so the
runtime is a load-bearing part of the explanation and not a
footnote. */}
Rewards and transfers are decoded against the chain's own metadata — the type registry
the node produces by running the runtime WASM in each block's state, not a schema
written here. This chain is on{' '}
<Link to={href({ chain, runtime: state.summary.spec_version })}>
{state.summary.spec_name ?? 'runtime'} v{state.summary.spec_version}
</Link>
.
</span>
)}
<span className="colophon">
{/* Its own line rather than a fourth item in the `space-between`
flow: the three above are caveats about the numbers, this is

View File

@@ -56,7 +56,6 @@ export function BlockPanel({
block,
targetSeconds,
onResolved,
onClose,
}: {
chain: string
/** Height or hash, as it appeared in the URL. */
@@ -65,7 +64,6 @@ export function BlockPanel({
targetSeconds: number
/** Called with the canonical hash once a height has resolved to one. */
onResolved: (hash: string) => void
onClose: () => void
}) {
const [detail, setDetail] = useState<BlockDetail | null>(null)
const [error, setError] = useState<string | null>(null)
@@ -122,21 +120,9 @@ export function BlockPanel({
{detail?.hash ?? ''}
</div>
</div>
<button
className="segmented"
style={{
padding: '7px 13px',
font: 'inherit',
fontSize: 12,
letterSpacing: '0.1em',
textTransform: 'uppercase',
color: 'var(--text-secondary)',
cursor: 'pointer',
}}
onClick={onClose}
>
Close
</button>
{/* No close button: this is a URL somebody navigated to, not a dialog
that opened over something they were reading. The back button and
the chain chip both already do what it would. */}
</div>
{error && <p className="empty">{error}</p>}