model bars: derive gradient from name (procedural, not per-name CSS)

The CSS-rule-per-canonical-name approach was wrong: any name the
producer publishes that wasn't in the hardcoded list (mlp_realistic,
cnn_oracle, knn_semi, anything new tomorrow) rendered grey because
no .model-fill.<name> rule matched.

Replace with a deterministic FNV-1a hash of the model string → hue,
applied inline as an OKLCH gradient when the row is created. Every
model string gets a stable, distinct color regardless of suffix or
case. Inline style beats any CSS rule, so this works whatever's in
dashboard.css.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Max Gorog 2026-05-08 20:00:42 -05:00
parent 53d2b80009
commit 984300ba21
2 changed files with 25 additions and 7 deletions

View file

@ -1760,24 +1760,42 @@ def train_nn(*, model, X_train, y_train, X_val, y_val,
root.innerHTML = ''; root.innerHTML = '';
root.appendChild(awaitingNote('awaiting model_metric events · turn demo on for examples')); root.appendChild(awaitingNote('awaiting model_metric events · turn demo on for examples'));
} }
// Derive a gradient deterministically from the model name. Any
// string the producer publishes (mlp / mlp_realistic / cnn_oracle
// / knn_semi / something we never anticipated) maps to a stable
// hue, so the bar always paints. Beats hardcoding CSS rules per
// known name and missing whatever the producer adds tomorrow.
function modelHue(s) {
// FNV-1a 32-bit, cheap and deterministic across browsers.
let h = 2166136261;
for (let i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i);
h = (h * 16777619) >>> 0;
}
return h % 360;
}
function gradientFor(model) {
const hue = modelHue(String(model));
return `linear-gradient(90deg, oklch(72% 0.18 ${hue}), oklch(46% 0.20 ${hue}))`;
}
function ensureRow(model) { function ensureRow(model) {
if (rows.has(model)) return rows.get(model); if (rows.has(model)) return rows.get(model);
if (root.querySelector('.awaiting')) root.innerHTML = ''; if (root.querySelector('.awaiting')) root.innerHTML = '';
const row = document.createElement('div'); row.className = 'model-row'; const row = document.createElement('div'); row.className = 'model-row';
row.innerHTML = ` row.innerHTML = `
<div class="model-name">${model}</div> <div class="model-name">${model}</div>
<div class="model-track"><div class="model-fill ${model}" style="width:0%"></div></div> <div class="model-track"><div class="model-fill" style="width:0%"></div></div>
<div class="model-acc">0.000</div>`; <div class="model-acc">0.000</div>`;
root.appendChild(row); root.appendChild(row);
const entry = { row, fill: row.querySelector('.model-fill'), acc: row.querySelector('.model-acc') }; const fill = row.querySelector('.model-fill');
// Inline style beats any CSS rule, so the gradient survives no
// matter what's in dashboard.css.
fill.style.background = gradientFor(model);
const entry = { row, fill, acc: row.querySelector('.model-acc') };
rows.set(model, entry); return entry; rows.set(model, entry); return entry;
} }
function render(model, accuracy) { function render(model, accuracy) {
const r = ensureRow(model); const r = ensureRow(model);
// Full 01 visible scale. The previous (acc-0.5)/0.5 mapping
// clamped honest-low cross-host F1s to 0% width and made the
// bars look unpopulated. Producer-side change — see
// docs/dashboard-request-scenes-7-8-12.md for context.
const visible = Math.max(0, Math.min(1, accuracy)); const visible = Math.max(0, Math.min(1, accuracy));
r.fill.style.width = (visible * 100).toFixed(1) + '%'; r.fill.style.width = (visible * 100).toFixed(1) + '%';
r.acc.textContent = accuracy.toFixed(3); r.acc.textContent = accuracy.toFixed(3);

View file

@ -648,6 +648,6 @@
</article> </article>
</div> </div>
<script src="/static/dashboard.js?v=960c0baa"></script> <script src="/static/dashboard.js?v=d16a0e1c"></script>
</body> </body>
</html> </html>