ci: format the frontend with prettier and gate it
`cargo fmt --check` has gated the Rust half since the first commit; nothing gated the TypeScript, so 25 of 31 files under web/src had drifted from the .prettierrc that was already sitting in the repo. `pnpm format:check` is now the counterpart, in the same step as `pnpm lint`. The reformat is whitespace only — the emitted bundle hashes identically before and after. web/.prettierignore excludes src/api/generated. Those files are written by ts-rs from the entities crate and CI already fails on any diff in them after `cargo test`; formatting them here would be reverted by the next regeneration and turn that drift gate red on every subsequent build. Verified by regenerating and confirming the tree stays clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5
This commit is contained in:
@@ -91,6 +91,11 @@ jobs:
|
||||
# already on PATH from `npm i -g pnpm` (gitea-runners.md §4).
|
||||
cd web
|
||||
pnpm install --frozen-lockfile
|
||||
# The web half's counterpart to `cargo fmt --check` above. It skips
|
||||
# src/api/generated (.prettierignore) — ts-rs owns those files and
|
||||
# regenerates them in its own style, so formatting them here would be
|
||||
# undone on the next `cargo test` and fail the drift gate above.
|
||||
pnpm format:check
|
||||
pnpm lint
|
||||
pnpm build
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Written by ts-rs from the Rust entities crate when `cargo test -p
|
||||
# blackbeard-entities` runs, and checked for drift in CI. Formatting them here
|
||||
# would be undone by the next regeneration, and the "generated types are
|
||||
# current" gate would then fail on every build.
|
||||
src/api/generated/
|
||||
|
||||
dist/
|
||||
@@ -10,6 +10,7 @@
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write src",
|
||||
"format:check": "prettier --check src",
|
||||
"typecheck": "tsc -b --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
+15
-11
@@ -56,7 +56,10 @@ export default function App() {
|
||||
// Default to the busiest chain we can actually track — the list is ordered
|
||||
// by node count, and an untracked chain has nothing behind it.
|
||||
const chain = route.chain ?? state.chains.find((c) => c.tracking === 'full')?.id ?? null
|
||||
const info = useMemo(() => state.chains.find((c) => c.id === chain) ?? null, [state.chains, chain])
|
||||
const info = useMemo(
|
||||
() => state.chains.find((c) => c.id === chain) ?? null,
|
||||
[state.chains, chain],
|
||||
)
|
||||
|
||||
useWatch(chain, route.window)
|
||||
|
||||
@@ -67,16 +70,14 @@ export default function App() {
|
||||
: `/${merged.chain ?? ''}/${merged.window}`
|
||||
}, [])
|
||||
|
||||
const selectMiner = useCallback(
|
||||
(miner: string) => {
|
||||
// Keep the chain and window; the miner panel opens above the board rather
|
||||
// than replacing the page, so the standings stay visible behind it.
|
||||
setRoute((r) => ({ ...r, miner }))
|
||||
},
|
||||
[],
|
||||
)
|
||||
const selectMiner = useCallback((miner: string) => {
|
||||
// Keep the chain and window; the miner panel opens above the board rather
|
||||
// than replacing the page, so the standings stay visible behind it.
|
||||
setRoute((r) => ({ ...r, miner }))
|
||||
}, [])
|
||||
|
||||
const interval = state.summary?.block_interval_seconds ?? state.summary?.target_block_time_seconds ?? 6
|
||||
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]!
|
||||
|
||||
return (
|
||||
@@ -199,7 +200,10 @@ function Sigil() {
|
||||
stroke="var(--data)"
|
||||
strokeWidth="1.6"
|
||||
/>
|
||||
<path d="M17 8 L24 12 V21 C24 26 20.5 29.5 17 31.5 C13.5 29.5 10 26 10 21 V12 Z" fill="var(--data-wash)" />
|
||||
<path
|
||||
d="M17 8 L24 12 V21 C24 26 20.5 29.5 17 31.5 C13.5 29.5 10 26 10 21 V12 Z"
|
||||
fill="var(--data-wash)"
|
||||
/>
|
||||
<path d="M17 11 V28" stroke="var(--accent)" strokeWidth="1.6" />
|
||||
<path d="M13 15 L17 11 L21 15" fill="none" stroke="var(--accent)" strokeWidth="1.6" />
|
||||
</svg>
|
||||
|
||||
@@ -50,11 +50,7 @@ export function BlockTicker({
|
||||
return (
|
||||
<div
|
||||
key={block.height}
|
||||
className={[
|
||||
'ticker-row',
|
||||
mine ? 'mine' : '',
|
||||
fresh.has(block.height) ? 'fresh' : '',
|
||||
]
|
||||
className={['ticker-row', mine ? 'mine' : '', fresh.has(block.height) ? 'fresh' : '']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
@@ -77,7 +73,9 @@ export function BlockTicker({
|
||||
{block.display}
|
||||
</a>
|
||||
<span className="ticker-gap">
|
||||
{block.gap_seconds !== null ? `+${seconds(block.gap_seconds)}` : ago(block.observed_at)}
|
||||
{block.gap_seconds !== null
|
||||
? `+${seconds(block.gap_seconds)}`
|
||||
: ago(block.observed_at)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -52,11 +52,7 @@ export function ChainSwitcher({
|
||||
<button
|
||||
key={chain.genesis ?? chain.id}
|
||||
type="button"
|
||||
className={[
|
||||
'chain-chip',
|
||||
isActive ? 'is-active' : '',
|
||||
tracked ? '' : 'is-untracked',
|
||||
]
|
||||
className={['chain-chip', isActive ? 'is-active' : '', tracked ? '' : 'is-untracked']
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
|
||||
@@ -60,7 +60,12 @@ export function MinerPanel({
|
||||
</h2>
|
||||
<div
|
||||
className="numeral"
|
||||
style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 4, overflowWrap: 'anywhere' }}
|
||||
style={{
|
||||
fontSize: 11,
|
||||
color: 'var(--text-muted)',
|
||||
marginTop: 4,
|
||||
overflowWrap: 'anywhere',
|
||||
}}
|
||||
>
|
||||
{miner}
|
||||
</div>
|
||||
@@ -68,7 +73,15 @@ export function MinerPanel({
|
||||
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
|
||||
<button
|
||||
className="segmented"
|
||||
style={{ padding: '7px 13px', font: 'inherit', fontSize: 12, letterSpacing: '0.1em', textTransform: 'uppercase', color: mine ? 'var(--accent-bright)' : 'var(--text-secondary)', cursor: 'pointer' }}
|
||||
style={{
|
||||
padding: '7px 13px',
|
||||
font: 'inherit',
|
||||
fontSize: 12,
|
||||
letterSpacing: '0.1em',
|
||||
textTransform: 'uppercase',
|
||||
color: mine ? 'var(--accent-bright)' : 'var(--text-secondary)',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
aria-pressed={mine}
|
||||
onClick={() => toggle(miner)}
|
||||
>
|
||||
@@ -76,7 +89,15 @@ export function MinerPanel({
|
||||
</button>
|
||||
<button
|
||||
className="segmented"
|
||||
style={{ padding: '7px 13px', font: 'inherit', fontSize: 12, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'var(--text-secondary)', cursor: 'pointer' }}
|
||||
style={{
|
||||
padding: '7px 13px',
|
||||
font: 'inherit',
|
||||
fontSize: 12,
|
||||
letterSpacing: '0.1em',
|
||||
textTransform: 'uppercase',
|
||||
color: 'var(--text-secondary)',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={onClose}
|
||||
>
|
||||
Close
|
||||
@@ -121,7 +142,9 @@ export function MinerPanel({
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-label">Best streak</div>
|
||||
<div className="stat-value">{row && row.best_streak > 1 ? `×${row.best_streak}` : '—'}</div>
|
||||
<div className="stat-value">
|
||||
{row && row.best_streak > 1 ? `×${row.best_streak}` : '—'}
|
||||
</div>
|
||||
<div className="stat-note">consecutive blocks</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -160,7 +160,13 @@ export function ShareChart({ points }: { points: MinerSeriesPoint[] }) {
|
||||
|
||||
{hover && (
|
||||
<>
|
||||
<line className="crosshair" x1={hover.x} x2={hover.x} y1={PAD.top} y2={chart.baseline} />
|
||||
<line
|
||||
className="crosshair"
|
||||
x1={hover.x}
|
||||
x2={hover.x}
|
||||
y1={PAD.top}
|
||||
y2={chart.baseline}
|
||||
/>
|
||||
{/* 2px surface ring so the marker stays legible where it sits on the
|
||||
line; the ring is part of the hit target, not just spacing. */}
|
||||
<circle className="marker" cx={hover.x} cy={hover.y} r={4.5} />
|
||||
@@ -196,7 +202,9 @@ export function ShareChart({ points }: { points: MinerSeriesPoint[] }) {
|
||||
transform: hover.x > width / 2 ? 'translate(-100%, 0)' : 'translate(10px, 0)',
|
||||
}}
|
||||
>
|
||||
<div className="tooltip-label">{timeLabel(new Date(hover.point.at).getTime(), chart.span)}</div>
|
||||
<div className="tooltip-label">
|
||||
{timeLabel(new Date(hover.point.at).getTime(), chart.span)}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{share(hover.point.share)}</strong> of blocks
|
||||
</div>
|
||||
|
||||
@@ -102,9 +102,7 @@ function TrendStat({
|
||||
{...(hero ? { hero } : {})}
|
||||
{...(reading ? { noteClass: 'reading' } : noteClass ? { noteClass } : {})}
|
||||
{...(title ? { title } : {})}
|
||||
{...(points
|
||||
? { trend: <Sparkline points={points} label={label} onProbe={onProbe} /> }
|
||||
: {})}
|
||||
{...(points ? { trend: <Sparkline points={points} label={label} onProbe={onProbe} /> } : {})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user