From 8c2f081063830573f1dd9694d174133aa2aab7ec Mon Sep 17 00:00:00 2001 From: NoeFabris Date: Mon, 11 May 2026 04:31:59 +0000 Subject: [PATCH] feat: add source freshness audit panel --- package.json | 3 +- public/index.html | 97 ++++++++++++++++++++++++++++++++++++++ scripts/check-frontend.mjs | 41 ++++++++++++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 scripts/check-frontend.mjs diff --git a/package.json b/package.json index c1d54d8..2f06ee5 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "type": "module", "private": true, "scripts": { - "validate:data": "node scripts/validate-data.mjs" + "validate:data": "node scripts/validate-data.mjs", + "check:frontend": "node scripts/check-frontend.mjs" }, "devDependencies": { "@types/bun": "latest", diff --git a/public/index.html b/public/index.html index 5ad879f..81ead67 100644 --- a/public/index.html +++ b/public/index.html @@ -143,6 +143,28 @@ .method-title{font-size:0.9rem;font-weight:600;margin-bottom:0.5rem} .method-desc{font-size:0.8rem;color:var(--text-secondary);line-height:1.6} +/* Freshness audit */ +.freshness-section{padding:2.5rem 0;border-top:1px solid var(--border)} +.freshness-panel{border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-elevated);overflow:hidden} +.freshness-head{padding:1.25rem 1.5rem;border-bottom:1px solid var(--border);display:flex;align-items:flex-end;justify-content:space-between;gap:1rem;flex-wrap:wrap} +.freshness-sub{font-size:0.82rem;color:var(--text-secondary);margin-top:0.35rem;max-width:680px;line-height:1.6} +.freshness-body{display:grid;grid-template-columns:1.1fr 1fr;gap:1px;background:var(--border)} +.freshness-card{background:var(--bg-elevated);padding:1.25rem} +.freshness-kpis{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--border);border-radius:var(--radius-sm);overflow:hidden;margin-bottom:1rem} +.freshness-kpi{background:var(--bg);padding:0.9rem;text-align:center;min-width:0} +.freshness-num{font-family:'JetBrains Mono',monospace;font-size:1.35rem;font-weight:800;letter-spacing:0} +.freshness-label{font-size:0.62rem;font-weight:700;letter-spacing:0.07em;text-transform:uppercase;color:var(--text-muted);margin-top:0.2rem} +.freshness-list{display:flex;flex-direction:column;gap:0.55rem} +.freshness-item{display:grid;grid-template-columns:6.4rem 1fr;gap:0.75rem;align-items:start;padding:0.65rem 0.75rem;border:1px solid var(--border);border-radius:var(--radius-xs);background:var(--bg);font-size:0.8rem} +.freshness-date{font-family:'JetBrains Mono',monospace;color:var(--accent);font-size:0.72rem;white-space:nowrap;padding-top:0.1rem} +.freshness-title{font-weight:600;margin-bottom:0.2rem} +.freshness-change{color:var(--text-secondary);line-height:1.5} +.freshness-muted{font-size:0.8rem;color:var(--text-muted);font-style:italic} +.freshness-stale{display:flex;flex-wrap:wrap;gap:0.4rem;margin-top:0.7rem} +.freshness-chip{display:inline-flex;align-items:center;gap:0.3rem;padding:0.3rem 0.5rem;border:1px solid var(--border);border-radius:100px;background:var(--bg);font-size:0.72rem;color:var(--text-secondary)} +.freshness-chip span{font-family:'JetBrains Mono',monospace;color:var(--text-muted);font-size:0.68rem} +@media(max-width:760px){.freshness-body{grid-template-columns:1fr}.freshness-kpis{grid-template-columns:1fr 1fr}.freshness-item{grid-template-columns:1fr}} + /* API section */ .api-section{padding:2rem 0;border-top:1px solid var(--border)} .api-card{display:flex;align-items:center;justify-content:space-between;background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--radius-sm);padding:1rem 1.25rem;flex-wrap:wrap;gap:0.75rem} @@ -353,6 +375,32 @@

Scorecard

+ +
+
+
+
+
+

Source Freshness Audit

+

Operational view of the company world model: verification coverage, stale entries, and recent data changes. This makes the backlog inspectable without reading raw JSON.

+
+ +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
@@ -503,6 +551,55 @@

Ecosystem

document.getElementById('key-insight').textContent=data.metadata.key_insight; document.getElementById('context').textContent=data.metadata.critical_context; +function daysBetween(a,b){ +const ms=Date.parse(a+'T00:00:00Z'); +const ns=Date.parse(b+'T00:00:00Z'); +if(Number.isNaN(ms)||Number.isNaN(ns))return null; +return Math.max(0,Math.round((ns-ms)/86400000)); +} + +function renderFreshnessAudit(){ +const asOf=data.metadata.last_updated||data.metadata.date||new Date().toISOString().slice(0,10); +const verified=devs.filter(d=>/^\d{4}-\d{2}-\d{2}$/.test(d.last_verified||'')); +const stale=devs.filter(d=>{ +const age=daysBetween(d.last_verified,asOf); +return age===null||age>30; +}).sort((a,b)=>{ +const aa=daysBetween(a.last_verified,asOf); +const bb=daysBetween(b.last_verified,asOf); +return (bb??9999)-(aa??9999); +}); +const sourced=devs.filter(d=>Array.isArray(d.sources)&&d.sources.length>0); +const updates=Array.isArray(data.metadata.update_history)?data.metadata.update_history.slice().reverse().slice(0,6):[]; +const pct=n=>Math.round((n/Math.max(1,total))*100); +document.getElementById('freshness-asof').textContent='As of '+asOf; +document.getElementById('freshness-kpis').innerHTML=[ +{num:pct(verified.length)+'%',label:'verified'}, +{num:pct(sourced.length)+'%',label:'sourced'}, +{num:stale.length,label:'>30d stale'} +].map(k=>`
${k.num}
${k.label}
`).join(''); +const staleEl=document.getElementById('freshness-stale-list'); +staleEl.innerHTML=stale.length +?stale.slice(0,12).map(d=>{ +const age=daysBetween(d.last_verified,asOf); +const ageText=age===null?'missing':age+'d'; +return ``; +}).join('') +:'
No entries older than 30 days.
'; +staleEl.querySelectorAll('.freshness-chip').forEach(btn=>{ +btn.addEventListener('click',()=>{ +const dev=devs.find(d=>d.name===btn.dataset.dev); +if(dev)openDrawer(dev); +}); +}); +const updatesEl=document.getElementById('freshness-updates'); +updatesEl.innerHTML=updates.length +?updates.map(u=>`
${escapeHtml(u.date||'unknown')}
${escapeHtml(u.developer||'World model')}
${escapeHtml(u.change||'Update recorded.')}${u.pr?' · '+escapeHtml(u.pr):''}${u.contributor?' · '+escapeHtml(u.contributor):''}
`).join('') +:'
No update history recorded yet.
'; +} + +renderFreshnessAudit(); + // Stats const statsEl=document.getElementById('stats'); const scores=[ diff --git a/scripts/check-frontend.mjs b/scripts/check-frontend.mjs new file mode 100644 index 0000000..9f407f3 --- /dev/null +++ b/scripts/check-frontend.mjs @@ -0,0 +1,41 @@ +#!/usr/bin/env node +import fs from "fs"; +import vm from "vm"; + +const FILE = new URL("../public/index.html", import.meta.url); +const html = fs.readFileSync(FILE, "utf8"); +const errors = []; + +function assert(condition, message) { + if (!condition) errors.push(message); +} + +for (const id of [ + "freshness-panel", + "freshness-body", + "freshness-updates", + "freshness-stale-list", +]) { + assert(html.includes(`id="${id}"`), `missing #${id}`); +} + +assert(html.includes("function renderFreshnessAudit"), "missing renderFreshnessAudit()"); + +const scriptMatch = html.match(/