From 289cabd21a58d5f8266710d3f605d6a0b0ec5c6c Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Sun, 26 Jul 2026 07:56:06 +0200 Subject: [PATCH] chore: strip em/en dashes from user-facing text (too AI-generated) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes em-dashes and en-dashes from every user-visible surface, replacing with commas / periods / colons contextually. Never a hyphen. Editorial preference documented in the user's global CLAUDE.md and already enforced by the spec-schema validator on bench YAMLs; this extends the same rule to markdown docs, hardcoded UI copy, meta titles, JSON-LD names, SVG aria-labels, provider registry descriptions. Scope: - README.md, AGENTS.md, docs/architecture.md (all prose in these files) - src/app/layout.tsx (RSS + JSON Feed titles) - src/app/mcp, rpc, hyperliquid/[slug], benchmarks/[slug], compare/[slug]/page.tsx (page titles, headings, jsonld names) - src/app/api/badge, api/sparkline (SVG titles + aria-labels) - src/data/provider-registry.ts (ParaSwap, Ondo OUSG, Maple SyrupUSDC) Skipped: - JSX comments ({/* ... */}) and inline // block comments (internal code documentation, not user-visible) - Design-vocabulary '—' glyphs used as 'no data' cell indicators (ledger-table, hl-cohort-leaderboard, etc.) - src/lib/jsonld.ts em-dash logic (operates ON the dash, not text) --- AGENTS.md | 2 +- README.md | 4 ++-- docs/architecture.md | 10 +++++----- src/app/api/badge/[slug]/[provider]/route.ts | 2 +- src/app/api/sparkline/[slug]/route.ts | 2 +- src/app/benchmarks/[slug]/page.tsx | 2 +- src/app/compare/[slug]/page.tsx | 2 +- src/app/hyperliquid/[slug]/page.tsx | 6 +++--- src/app/layout.tsx | 4 ++-- src/app/mcp/page.tsx | 2 +- src/app/rpc/page.tsx | 4 ++-- src/data/provider-registry.ts | 6 +++--- 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6915d2a9..88882fa7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,6 +21,6 @@ Two long-lived branches: **Rules:** - Never push directly to `main` or `dev`. PRs only. -- The staging URL emits `` and serves `robots.txt: Disallow /` — see `src/app/robots.ts` and `src/app/layout.tsx`. Don't undo without thinking through duplicate-content SEO impact on the prod domain. Vercel Preview SSO is disabled (URL is publicly shareable), so the noindex is the only thing keeping crawlers out. +- The staging URL emits `` and serves `robots.txt: Disallow /`, see `src/app/robots.ts` and `src/app/layout.tsx`. Don't undo without thinking through duplicate-content SEO impact on the prod domain. Vercel Preview SSO is disabled (URL is publicly shareable), so the noindex is the only thing keeping crawlers out. - Hotfix path: if prod is on fire and waiting through `dev` is unacceptable, open the fix PR directly against `main`, merge (CI deploys automatically), then immediately fast-forward `dev` from `main` (`git checkout dev && git merge --ff-only main && git push`) so the two branches don't diverge. - Required GitHub secrets (repo-level, not environment-scoped): `VERCEL_TOKEN`, `VERCEL_ORG_ID`, `VERCEL_PROJECT_ID`. diff --git a/README.md b/README.md index 8288e9d6..219c7f70 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![License: MIT](https://img.shields.io/badge/code-MIT-blue.svg)](./LICENSE) [![Data: CC-BY-4.0](https://img.shields.io/badge/data-CC--BY--4.0-lightgrey.svg)](https://creativecommons.org/licenses/by/4.0/) [![Benchmarks](https://img.shields.io/badge/benchmarks-94_live-green.svg)](https://openchainbench.com/benchmarks) [![MCP](https://img.shields.io/badge/MCP-ready-purple.svg)](https://openchainbench.com/mcp) [![llms.txt](https://img.shields.io/badge/llms.txt-yes-orange.svg)](https://openchainbench.com/llms.txt) -OpenChainBench publishes one benchmark at a time, each shipping with the harness that produces its data. The goal is to make performance an observable property of crypto infra — measured in the open, by anyone who wants to add a provider or a metric. +OpenChainBench publishes one benchmark at a time, each shipping with the harness that produces its data. The goal is to make performance an observable property of crypto infra, measured in the open, by anyone who wants to add a provider or a metric. Community-run, MIT-licensed harnesses + CC-BY-4.0 data, PRs from any party including the providers we benchmark. @@ -14,7 +14,7 @@ Community-run, MIT-licensed harnesses + CC-BY-4.0 data, PRs from any party inclu - **50** self-contained Go harnesses, each shipping a `/metrics` Prometheus endpoint - **32** curated question/answer pages · **23** "alternatives to X" landing pages - **26** machine-readable API routes (citation, badges, MCP, feeds, per-format quotes) -- One shared Prometheus, one materialization worker, CDN-fastpath snapshot store — cold reads in < 100 ms edge, no origin fan-out on hot paths +- One shared Prometheus, one materialization worker, CDN-fastpath snapshot store: cold reads in < 100 ms edge, no origin fan-out on hot paths ## Quickstart diff --git a/docs/architecture.md b/docs/architecture.md index 75abcbfd..94116edc 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -70,7 +70,7 @@ build clones the OCB `dev` branch fresh). ## Known constraints - Prometheus must be reachable from the materialization worker; the site only needs the Redis store (plus Prometheus for the live fallback path). -- Worker sweep cost grows linearly with providers (one Prometheus call per provider per metric per benchmark) — the heaviest bench, hyperliquid-frontends, declares 100+ providers. The Prom client bounds concurrency with a global query semaphore. +- Worker sweep cost grows linearly with providers (one Prometheus call per provider per metric per benchmark), the heaviest bench, hyperliquid-frontends, declares 100+ providers. The Prom client bounds concurrency with a global query semaphore. - `generateStaticParams` reads the YAML filenames directly so the route table is stable even when Prometheus or the store is offline. - Spec changes that alter a bench's provider list require a worker image rebuild to show up on the site (the worker bakes its OCB checkout at build time). @@ -105,7 +105,7 @@ The live dashboard at [openchainbench.com](https://openchainbench.com) has its * ``` The browser opens a WebSocket **directly to the Railway relay**. Vercel -Functions are never invoked for live data — keeps cost flat at ~$5/mo +Functions are never invoked for live data, keeps cost flat at ~$5/mo regardless of viewer count. ### Why a separate relay (not Vercel SSE) @@ -122,9 +122,9 @@ Three message types arrive on `/ws`, all JSON: | `type` | When | Payload | |---|---|---| -| `snapshot` | First message on every connect | `{ buckets: [{ ts, perChain }], nowMs }` — the last 10 min of incremental per-chain volume, padded with empty buckets on the left so the chart is full from t=0. | -| `swap` | Per real trade (filtered: `buy`/`sell`, `operation=regular\|arbitrage`) | `{ chain, pool, pair, exchange, side, usd, hash, onChainMs, mobulaMs, receivedMs }` — enriched with pair name + DEX from a top-N catalog fetched at boot. | -| `stats` | Every 1 s | `{ global: {vol24h, trades24h, …, mcap, byChain[]}, live: {swaps, vol, chains}, nowMs }` — ground-truth global stats from REST + per-session live counters. | +| `snapshot` | First message on every connect | `{ buckets: [{ ts, perChain }], nowMs }`, the last 10 min of incremental per-chain volume, padded with empty buckets on the left so the chart is full from t=0. | +| `swap` | Per real trade (filtered: `buy`/`sell`, `operation=regular\|arbitrage`) | `{ chain, pool, pair, exchange, side, usd, hash, onChainMs, mobulaMs, receivedMs }`, enriched with pair name + DEX from a top-N catalog fetched at boot. | +| `stats` | Every 1 s | `{ global: {vol24h, trades24h, …, mcap, byChain[]}, live: {swaps, vol, chains}, nowMs }`, ground-truth global stats from REST + per-session live counters. | The browser snapshots the first message, then accumulates `swap` events into incremental buckets matching the relay's bucket boundaries (using the snapshot's `nowMs` to learn the relay/browser clock offset). diff --git a/src/app/api/badge/[slug]/[provider]/route.ts b/src/app/api/badge/[slug]/[provider]/route.ts index d699b26b..ff0c0ba1 100644 --- a/src/app/api/badge/[slug]/[provider]/route.ts +++ b/src/app/api/badge/[slug]/[provider]/route.ts @@ -385,7 +385,7 @@ function placeholderSvg(benchTitle: string): NextResponse { Math.max(W_MIN, TEXT_X + title.length * CH_11 + 30), ); const svg = ` - + OpenChainBench. ${escapeXml(title)}. Measurement pending. diff --git a/src/app/api/sparkline/[slug]/route.ts b/src/app/api/sparkline/[slug]/route.ts index 49fbbf0a..fe3250d9 100644 --- a/src/app/api/sparkline/[slug]/route.ts +++ b/src/app/api/sparkline/[slug]/route.ts @@ -137,7 +137,7 @@ export async function GET( `stroke-linejoin="round"/>`; } const title = xmlEscape( - `${bench.title} — 24h sparkline${top ? ` (${top.name})` : ""}`, + `${bench.title}: 24h sparkline${top ? ` (${top.name})` : ""}`, ); const svg = `\n` + diff --git a/src/app/benchmarks/[slug]/page.tsx b/src/app/benchmarks/[slug]/page.tsx index f2f72fd7..fd197b5d 100644 --- a/src/app/benchmarks/[slug]/page.tsx +++ b/src/app/benchmarks/[slug]/page.tsx @@ -451,7 +451,7 @@ export default async function BenchmarkPage({ benchmark.faq, benchmarkUrl, null, - `${benchmark.title} — frequently asked questions`, + `${benchmark.title}: frequently asked questions`, ); return ( diff --git a/src/app/compare/[slug]/page.tsx b/src/app/compare/[slug]/page.tsx index a3d28e45..4abcd033 100644 --- a/src/app/compare/[slug]/page.tsx +++ b/src/app/compare/[slug]/page.tsx @@ -803,7 +803,7 @@ export default async function ComparePage({ // FAQPage parent even when Question.name is set. Add here + on // Answer nodes so the compare page matches the shared FAQ builder // in src/lib/jsonld.ts. - name: `${a.name} vs ${b.name} — frequently asked questions`, + name: `${a.name} vs ${b.name}: frequently asked questions`, mainEntity: faqEntries.map((e) => ({ "@type": "Question", name: e.q, diff --git a/src/app/hyperliquid/[slug]/page.tsx b/src/app/hyperliquid/[slug]/page.tsx index b41ec784..de10af5e 100644 --- a/src/app/hyperliquid/[slug]/page.tsx +++ b/src/app/hyperliquid/[slug]/page.tsx @@ -65,7 +65,7 @@ export async function generateMetadata({ if (!frontend) { return pageMetadata({ path: `/hyperliquid/${slug}`, - title: `${displayName} — Hyperliquid frontend`, + title: `${displayName}, Hyperliquid frontend`, description: `${displayName} on Hyperliquid: 12-month builder fees, volume and first-active date. Rolling 30-day metrics refresh as data becomes available.`, }); } @@ -78,7 +78,7 @@ export async function generateMetadata({ )}). 12-month history with volume, fees and first-active date.`; return pageMetadata({ path: `/hyperliquid/${slug}`, - title: `${displayName} — Hyperliquid frontend`, + title: `${displayName}, Hyperliquid frontend`, description, }); } @@ -157,7 +157,7 @@ export default async function HlFrontendPage({ "@context": "https://schema.org", "@type": "Dataset", "@id": `${pageUrl}#dataset`, - name: `${frontend.name} — HyperLiquid frontend revenue + volume`, + name: `${frontend.name}, HyperLiquid frontend revenue + volume`, description: `Daily HyperLiquid frontend fees and volume for ${frontend.name}, sourced from the OpenChainBench hyperliquid-frontends benchmark. First measured ${firstDay}.`, url: pageUrl, identifier: slug, diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 450ff7bd..0d597207 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -198,7 +198,7 @@ export default async function RootLayout({ {/* JSON Feed 1.1 companion. Modern feed readers + AI agent @@ -207,7 +207,7 @@ export default async function RootLayout({

- No deeplink yet — the fastest path is{" "} + No deeplink yet. The fastest path is{" "} Settings → Developer → Model Context Protocol → Add server {" "} diff --git a/src/app/rpc/page.tsx b/src/app/rpc/page.tsx index a24fdefd..a1dbce12 100644 --- a/src/app/rpc/page.tsx +++ b/src/app/rpc/page.tsx @@ -112,7 +112,7 @@ export default async function RpcHubPage() { the same probe: one identical eth_blockNumber call every 15 seconds from 3 regions (N. Virginia, Amsterdam, Singapore). The matrix below folds the per-chain leaderboards - into one view — fastest provider per chain, fastest per region, + into one view: fastest provider per chain, fastest per region, and which gateway covers your whole multichain stack. Headline numbers are 24h p50 round-trip latency; methodology and exclusion rules live on the{" "} @@ -205,7 +205,7 @@ export default async function RpcHubPage() { Data warming up

- The cross-chain snapshot has not been published yet — the + The cross-chain snapshot has not been published yet. The materialize worker writes it every minute once the RPC cluster is sweeping. The per-chain leaderboards are already live on their bench pages: diff --git a/src/data/provider-registry.ts b/src/data/provider-registry.ts index 366f05d8..4b11c87d 100644 --- a/src/data/provider-registry.ts +++ b/src/data/provider-registry.ts @@ -149,7 +149,7 @@ export const PROVIDER_REGISTRY: Record = { "DEX aggregator and DeFi middleware layer. The Augustus Router splits orders across AMMs, PMMs, and direct pools on 10+ EVM chains to minimize price impact and gas costs. Also offers a developer SDK for integrating best-execution routing into dApps and wallets.", twitter: "@paraswap", longDescription: - "ParaSwap aggregates liquidity from Uniswap, Curve, Balancer, AAVE, and dozens of other AMMs and market makers via its Augustus Router. Trades are split across multiple paths atomically, selecting the combination that maximises net output after gas. ParaSwap also runs a delta trading layer — a private RFQ network where professional market makers post competitive quotes for large orders, bypassing on-chain pools entirely. The SDK and API are used by Ledger Live, Safe, and other wallets as the routing engine behind their swap UI.", + "ParaSwap aggregates liquidity from Uniswap, Curve, Balancer, AAVE, and dozens of other AMMs and market makers via its Augustus Router. Trades are split across multiple paths atomically, selecting the combination that maximises net output after gas. ParaSwap also runs a delta trading layer: a private RFQ network where professional market makers post competitive quotes for large orders, bypassing on-chain pools entirely. The SDK and API are used by Ledger Live, Safe, and other wallets as the routing engine behind their swap UI.", docs: "https://developers.paraswap.network", }, bebop: { @@ -1793,7 +1793,7 @@ export const PROVIDER_REGISTRY: Record = { ousg: { url: "https://ondo.finance/ousg", description: - "Ondo OUSG is a tokenized short-term U.S. Treasury fund. NAV appreciation model — the share price grows daily as yield accrues. Delivered yield on bench 089 is read from Ondo's on-chain oracle (Aave IPriceOracle interface).", + "Ondo OUSG is a tokenized short-term U.S. Treasury fund. NAV appreciation model: the share price grows daily as yield accrues. Delivered yield on bench 089 is read from Ondo's on-chain oracle (Aave IPriceOracle interface).", twitter: "@OndoFinance", docs: "https://docs.ondo.finance/", }, @@ -1807,7 +1807,7 @@ export const PROVIDER_REGISTRY: Record = { "syrup-usdc": { url: "https://syrup.fi", description: - "Maple SyrupUSDC is an ERC-4626 vault whose underlying is a book of institutional USDC loans. Not a T-bill wrapper — yield reflects loan-book performance. Delivered yield on bench 089 reads convertToAssets(1 share) at latest and t-30d blocks and compares to the pool's base APY (excludes SYRUP token rewards).", + "Maple SyrupUSDC is an ERC-4626 vault whose underlying is a book of institutional USDC loans. Not a T-bill wrapper. Yield reflects loan-book performance. Delivered yield on bench 089 reads convertToAssets(1 share) at latest and t-30d blocks and compares to the pool's base APY (excludes SYRUP token rewards).", twitter: "@maplefinance", docs: "https://maplefinance.gitbook.io/maple/", },