OverviewFilesCommitsIssuesDocsReleasesPeers

knowledge-ratchet commits / aa935f0

Improve chart readability — Y-axis labels, gridlines, value annotations
- All charts now have Y-axis labels with gridlines - Data point values shown above each point - Format hints: pct, int, auto per chart - Bar chart shows value labels on each bar - Fixed "loops 28.000" → integer format - Added descriptive subtitle for cold visitors - Increased chart heights for better spacing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
aa935f0792f2a14ff13d1b02da14f0f2905277d5
by plankatron <fleeky@gmail.com> · 2026-04-07T23:36:13.000Z

Changed files (1) +104 -37

dashboard.js +104 -37
……@@ -175,6 +175,24 @@ function generate () {
175175 const liveTraj = liveSession?.trajectory === 'improving' ? '▲ improving' : liveSession?.trajectory === 'declining' ? '▼ declining' : '▬ stable'
176176 const liveTrajColor = liveSession?.trajectory === 'improving' ? 'green' : liveSession?.trajectory === 'declining' ? 'red' : 'dim'
177177
178 // Live learning rate
179 let liveLearningRate = null
180 if (liveSession?.turnScores) {
181 let learned = 0, failed = 0
182 const ts = liveSession.turnScores
183 for (let i = 0; i < ts.length; i++) {
184 if (ts[i] === 0.2) {
185 const after = ts.slice(i + 1, i + 4)
186 if (after.includes(0.2)) failed++
187 else learned++
188 }
189 }
190 const total = learned + failed
191 if (total > 0) liveLearningRate = Math.round(learned / total * 100)
192 }
193 const liveLearningStr = liveLearningRate !== null ? `${liveLearningRate}% learning` : ''
194 const liveLearningColor = liveLearningRate > 70 ? 'green' : liveLearningRate > 40 ? 'yellow' : 'red'
195
178196 // ─── Generate HTML ───
179197 const html = `<!DOCTYPE html>
180198<html lang="en">
……@@ -217,7 +235,7 @@ function generate () {
217235</head>
218236<body>
219237<h1>Knowledge Ratchet</h1>
220<h2>Intelligence quality tracking — generated ${new Date().toISOString().slice(0, 19)}</h2>
238<h2>Self-improving AI quality system — tracks corrections, detects failure loops, auto-generates prevention rules</h2>
221239
222240${liveSession && liveSession.turns > 0 ? `
223241<div class="card" style="border-color: ${liveBorder}; margin-bottom: 16px;">
……@@ -227,6 +245,7 @@ ${liveSession && liveSession.turns > 0 ? `
227245 <div><span class="green" style="font-size:20px">${liveSession.approvals} approved</span> <span class="red" style="font-size:20px">${liveSession.corrections} corrected</span><span class="dim"> in ${liveSession.turns} turns</span></div>
228246 <div><span class="dim">${liveSession.toolCalls} tools</span></div>
229247 <div><span class="${liveTrajColor}">${liveTraj}</span></div>
248 ${liveLearningRate !== null ? `<div><span class="${liveLearningColor}" style="font-size:18px">${liveLearningStr}</span></div>` : ''}
230249 </div>
231250</div>
232251` : ''}
……@@ -258,7 +277,7 @@ ${liveSession && liveSession.turns > 0 ? `
258277<!-- Weekly Fused Score Trend -->
259278<div class="chart-container">
260279 <div class="card-title">Weekly Fused Score Trend</div>
261 ${renderLineChart(weeklyData.map(w => w.score), weeklyData.map(w => w.week), { height: 120, color: '#58a6ff', label: 'score' })}
280 ${renderLineChart(weeklyData.map(w => w.score), weeklyData.map(w => w.week), { height: 140, color: '#58a6ff', label: 'Fused Score', format: 'auto' })}
262281</div>
263282
264283<!-- Weekly Correction Density -->
……@@ -273,7 +292,7 @@ ${liveSession && liveSession.turns > 0 ? `
273292 ${renderLineChart(
274293 Object.entries(loopsWeekly).sort().map(([, v]) => v.total),
275294 Object.entries(loopsWeekly).sort().map(([k]) => k),
276 { height: 100, color: '#f85149', label: 'loops' }
295 { height: 120, color: '#f85149', label: 'Loops Per Week', format: 'int' }
277296 )}
278297</div>
279298
……@@ -334,7 +353,7 @@ ${benchmarkHistory.length >= 2 ? `
334353 ${renderLineChart(
335354 benchmarkHistory.filter(h => h.novelRate != null).map(h => h.novelRate),
336355 benchmarkHistory.filter(h => h.novelRate != null).map(h => h.date),
337 { height: 100, color: '#3fb950', label: 'novel rate' }
356 { height: 120, color: '#3fb950', label: 'Novel Rate', format: 'pct' }
338357 )}
339358 </div>
340359 <div class="card">
……@@ -342,7 +361,7 @@ ${benchmarkHistory.length >= 2 ? `
342361 ${renderLineChart(
343362 benchmarkHistory.filter(h => h.density != null).map(h => h.density),
344363 benchmarkHistory.filter(h => h.density != null).map(h => h.date),
345 { height: 100, color: '#f85149', label: 'density' }
364 { height: 120, color: '#f85149', label: 'Density', format: 'pct' }
346365 )}
347366 </div>
348367 <div class="card">
……@@ -350,7 +369,7 @@ ${benchmarkHistory.length >= 2 ? `
350369 ${renderLineChart(
351370 benchmarkHistory.filter(h => h.avgResolution != null).map(h => h.avgResolution),
352371 benchmarkHistory.filter(h => h.avgResolution != null).map(h => h.date),
353 { height: 100, color: '#d29922', label: 'turns' }
372 { height: 120, color: '#d29922', label: 'Turns to Resolve', format: 'auto' }
354373 )}
355374 </div>
356375 <div class="card">
……@@ -358,7 +377,7 @@ ${benchmarkHistory.length >= 2 ? `
358377 ${renderLineChart(
359378 benchmarkHistory.filter(h => h.rulesCount != null).map(h => h.rulesCount),
360379 benchmarkHistory.filter(h => h.rulesCount != null).map(h => h.date),
361 { height: 100, color: '#58a6ff', label: 'rules' }
380 { height: 120, color: '#58a6ff', label: 'Rules', format: 'int' }
362381 )}
363382 </div>
364383</div>
……@@ -434,64 +453,112 @@ if (serveArg) {
434453// ─── SVG Chart Renderers ───
435454
436455function renderLineChart (values, labels, opts = {}) {
437 const { height = 100, color = '#58a6ff', label = '' } = opts
456 const { height = 120, color = '#58a6ff', label = '', format = 'auto' } = opts
438457 if (values.length === 0) return '<div class="dim">No data</div>'
439458
440459 const w = 600
441460 const h = height
442 const pad = 30
443 const min = Math.min(...values) * 0.95
444 const max = Math.max(...values) * 1.05
461 const padL = 50 // left pad for Y-axis labels
462 const padR = 20
463 const padT = 25
464 const padB = 25
465 const chartW = w - padL - padR
466 const chartH = h - padT - padB
467
468 const rawMin = Math.min(...values)
469 const rawMax = Math.max(...values)
470 const margin = (rawMax - rawMin) * 0.15 || 0.05
471 const min = rawMin - margin
472 const max = rawMax + margin
445473 const range = max - min || 1
446474
475 // Format values for display
476 function fmt (v) {
477 if (format === 'pct') return (v * 100).toFixed(0) + '%'
478 if (format === 'int') return Math.round(v).toString()
479 if (v >= 100) return Math.round(v).toString()
480 if (v >= 1) return v.toFixed(1)
481 return (v * 100).toFixed(0) + '%'
482 }
483
484 // Y-axis: 3-4 gridlines with labels
485 const gridLines = []
486 const steps = 4
487 for (let i = 0; i <= steps; i++) {
488 const v = min + (range * i / steps)
489 const y = h - padB - (i / steps) * chartH
490 gridLines.push({ v, y })
491 }
492
447493 const points = values.map((v, i) => {
448 const x = pad + (i / Math.max(1, values.length - 1)) * (w - pad * 2)
449 const y = h - pad - ((v - min) / range) * (h - pad * 2)
450 return `${x},${y}`
494 const x = padL + (i / Math.max(1, values.length - 1)) * chartW
495 const y = h - padB - ((v - min) / range) * chartH
496 return { x, y, v }
451497 })
452498
453 const labelElements = labels.map((l, i) => {
499 const xLabels = labels.map((l, i) => {
454500 if (i % Math.max(1, Math.floor(labels.length / 6)) !== 0) return ''
455 const x = pad + (i / Math.max(1, values.length - 1)) * (w - pad * 2)
456 return `<text x="${x}" y="${h - 5}" fill="#484f58" font-size="10" text-anchor="middle">${l.slice(5)}</text>`
501 const x = padL + (i / Math.max(1, values.length - 1)) * chartW
502 return `<text x="${x}" y="${h - 3}" fill="#484f58" font-size="10" text-anchor="middle">${l.slice(5)}</text>`
457503 }).join('')
458504
459505 return `<svg viewBox="0 0 ${w} ${h}" xmlns="http://www.w3.org/2000/svg">
460 <polyline points="${points.join(' ')}" fill="none" stroke="${color}" stroke-width="2"/>
461 ${values.map((v, i) => {
462 const x = pad + (i / Math.max(1, values.length - 1)) * (w - pad * 2)
463 const y = h - pad - ((v - min) / range) * (h - pad * 2)
464 return `<circle cx="${x}" cy="${y}" r="3" fill="${color}"/>`
465 }).join('')}
466 ${labelElements}
467 <text x="${pad}" y="15" fill="#8b949e" font-size="11">${label} ${values[values.length - 1]?.toFixed(3) || ''}</text>
506 ${gridLines.map(g => `
507 <line x1="${padL}" y1="${g.y}" x2="${w - padR}" y2="${g.y}" stroke="#21262d" stroke-width="1"/>
508 <text x="${padL - 5}" y="${g.y + 4}" fill="#484f58" font-size="9" text-anchor="end">${fmt(g.v)}</text>
509 `).join('')}
510 <polyline points="${points.map(p => p.x + ',' + p.y).join(' ')}" fill="none" stroke="${color}" stroke-width="2"/>
511 ${points.map(p => `<circle cx="${p.x}" cy="${p.y}" r="3" fill="${color}"><title>${fmt(p.v)}</title></circle>`).join('')}
512 ${points.map(p => `<text x="${p.x}" y="${p.y - 8}" fill="${color}" font-size="9" text-anchor="middle">${fmt(p.v)}</text>`).join('')}
513 ${xLabels}
514 <text x="${padL}" y="14" fill="#8b949e" font-size="11">${label}</text>
468515 </svg>`
469516}
470517
471518function renderBarChart (weeklyData, opts = {}) {
472 const { height = 120 } = opts
519 const { height = 140 } = opts
473520 if (weeklyData.length === 0) return '<div class="dim">No data</div>'
474521
475522 const w = 600
476523 const h = height
477 const pad = 30
478 const barW = Math.max(8, (w - pad * 2) / weeklyData.length / 2 - 2)
479
480 const maxVal = Math.max(...weeklyData.map(d => d.corrections + d.approvals), 1)
524 const padL = 50
525 const padR = 20
526 const padT = 25
527 const padB = 25
528 const chartW = w - padL - padR
529 const chartH = h - padT - padB
530 const barW = Math.max(8, chartW / weeklyData.length / 2 - 2)
531
532 const maxVal = Math.max(...weeklyData.map(d => Math.max(d.corrections, d.approvals)), 1)
533
534 // Y-axis gridlines
535 const gridLines = []
536 const steps = 4
537 for (let i = 0; i <= steps; i++) {
538 const v = Math.round(maxVal * i / steps)
539 const y = h - padB - (i / steps) * chartH
540 gridLines.push({ v, y })
541 }
481542
482543 return `<svg viewBox="0 0 ${w} ${h}" xmlns="http://www.w3.org/2000/svg">
544 ${gridLines.map(g => `
545 <line x1="${padL}" y1="${g.y}" x2="${w - padR}" y2="${g.y}" stroke="#21262d" stroke-width="1"/>
546 <text x="${padL - 5}" y="${g.y + 4}" fill="#484f58" font-size="9" text-anchor="end">${g.v}</text>
547 `).join('')}
483548 ${weeklyData.map((d, i) => {
484 const x = pad + (i / Math.max(1, weeklyData.length - 1)) * (w - pad * 2)
485 const corrH = (d.corrections / maxVal) * (h - pad * 2)
486 const apprH = (d.approvals / maxVal) * (h - pad * 2)
549 const x = padL + (i / Math.max(1, weeklyData.length - 1)) * chartW
550 const corrH = (d.corrections / maxVal) * chartH
551 const apprH = (d.approvals / maxVal) * chartH
487552 return `
488 <rect x="${x - barW}" y="${h - pad - corrH}" width="${barW}" height="${corrH}" fill="#da3633" opacity="0.7"/>
489 <rect x="${x}" y="${h - pad - apprH}" width="${barW}" height="${apprH}" fill="#238636" opacity="0.7"/>
553 <rect x="${x - barW}" y="${h - padB - corrH}" width="${barW}" height="${corrH}" fill="#da3633" opacity="0.7"/>
554 <rect x="${x}" y="${h - padB - apprH}" width="${barW}" height="${apprH}" fill="#238636" opacity="0.7"/>
555 <text x="${x - barW/2}" y="${h - padB - corrH - 3}" fill="#da3633" font-size="8" text-anchor="middle">${d.corrections}</text>
556 <text x="${x + barW/2}" y="${h - padB - apprH - 3}" fill="#238636" font-size="8" text-anchor="middle">${d.approvals}</text>
490557 <text x="${x}" y="${h - 5}" fill="#484f58" font-size="9" text-anchor="middle">${d.week.slice(5)}</text>
491558 `
492559 }).join('')}
493 <text x="${pad}" y="15" fill="#da3633" font-size="11">■ corrections</text>
494 <text x="${pad + 100}" y="15" fill="#238636" font-size="11">■ approvals</text>
560 <text x="${padL}" y="14" fill="#da3633" font-size="11">■ corrections</text>
561 <text x="${padL + 100}" y="14" fill="#238636" font-size="11">■ approvals</text>
495562 </svg>`
496563}
497564