diff --git a/package.json b/package.json index 2f06ee5..6bc38f4 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "type": "module", "private": true, "scripts": { + "check:dashboard": "node scripts/check-dashboard-source.mjs", "validate:data": "node scripts/validate-data.mjs", "check:frontend": "node scripts/check-frontend.mjs" }, diff --git a/public/index.html b/public/index.html index 919273c..b791343 100644 --- a/public/index.html +++ b/public/index.html @@ -705,7 +705,7 @@

Ecosystem

Bitcoin Quantum Readiness${comp.current_level}
-
${voiced.score} / 100 voiced urgency
(among developers who voiced a position)

${voiced.voices_count} of ${data.length} developers have voiced positions
+
${voiced.score} / 100 voiced urgency
(among developers who voiced a position)

${voiced.voices_count} of ${total} developers have voiced positions
${coverage.score}% coverage
${coverage.silent} of ${coverage.total} developers have said nothing

${comp.formula}

@@ -1200,10 +1200,11 @@

Ecosystem

// Ranked devs only (not notable) const ranked = devs.filter(d => !d.notable && d.rank >= 1); + const rankMax = Math.max(...ranked.map(d => d.rank), 1); bubbles = []; ranked.forEach(dev => { - const x = padL + ((dev.rank - 1) / (total - 1)) * plotW; + const x = padL + ((dev.rank - 1) / Math.max(rankMax - 1, 1)) * plotW; const y = padT + plotH - ((dev.quantum_urgency_score - 1) / 4) * plotH; const vol = dev.pq_work_volume || 0; const r = Math.max(6, Math.min(18, 6 + vol * 2.5)); diff --git a/scripts/check-dashboard-source.mjs b/scripts/check-dashboard-source.mjs new file mode 100644 index 0000000..843a438 --- /dev/null +++ b/scripts/check-dashboard-source.mjs @@ -0,0 +1,37 @@ +#!/usr/bin/env node +import fs from "fs"; + +const html = fs.readFileSync(new URL("../public/index.html", import.meta.url), "utf8"); +const errors = []; + +function assert(condition, message) { + if (!condition) errors.push(message); +} + +assert( + !html.includes("${data.length} developers have voiced positions"), + "dashboard must not read data.length; the dataset is an object with metadata/developers", +); + +assert( + html.includes("${voiced.voices_count} of ${total} developers have voiced positions"), + "voiced-position copy should use metadata.total_assessed", +); + +assert( + html.includes("const rankMax = Math.max(...ranked.map(d => d.rank), 1);"), + "bubble chart should compute its influence scale from ranked entries", +); + +assert( + html.includes("Math.max(rankMax - 1, 1)"), + "bubble chart should scale x positions against the max ranked position, not total assessed entries", +); + +if (errors.length) { + console.error(`check-dashboard-source failed with ${errors.length} error(s):`); + for (const error of errors) console.error(`- ${error}`); + process.exit(1); +} + +console.log("check-dashboard-source passed");