// Dashboard — full Claude Design report viewer, wired to real job data // Preview fallback helper — reads from window.FOSSYL_DEMO (set by graph.jsx). // Hardcoded fallback matches graph.jsx RISK_CLUSTERS sums so both files stay in sync. function D(key) { return (window.FOSSYL_DEMO || { total:145, ttw:125, diverged:8, synced:12, ttwPct:86, edges:481, elapsed:52, deathKb:187 })[key]; } const DASH_API = 'https://r5vefiej3l.execute-api.eu-central-1.amazonaws.com/prod'; const DASH_COG = '7nffjheavp985k7b4bgug45jf4'; function dashToken() { const last = localStorage.getItem(`CognitoIdentityServiceProvider.${DASH_COG}.LastAuthUser`); if (last) { const t = localStorage.getItem(`CognitoIdentityServiceProvider.${DASH_COG}.${last}.idToken`); if (t) return t; } return window.getIdToken ? window.getIdToken() : ''; } async function dashApi(path) { const res = await fetch(`${DASH_API}${path}`, { headers: { Authorization: `Bearer ${dashToken()}` } }); const d = await res.json().catch(() => ({})); if (!res.ok) throw new Error(d.error || `HTTP ${res.status}`); return d; } function getActiveJobId() { return localStorage.getItem('fossyl_active_job') || ''; } function Dashboard() { const { go } = useNav(); const { toast } = useToast(); const [section, setSection] = useState('divergence'); const [job, setJob] = useState(null); const [downloading, setDownloading] = useState(false); const jobId = getActiveJobId(); // If no active job, send to archive useEffect(() => { if (!jobId) go('reports'); }, []); useEffect(() => { if (!jobId) return; dashApi(`/status/${jobId}`) .then(data => { setJob(data); if (window.validateRealStats) window.validateRealStats({ ttw: data.ttw_count || 0, diverged: data.diverged_count || 0, synced: data.synced_count || 0, artifacts:data.artifact_count || 0, }); }) .catch(() => {}); }, [jobId]); const handleDownload = async () => { setDownloading(true); try { const d = await dashApi(`/download/${jobId}`); window.location.href = d.download_url; } catch (e) { toast(e.message || 'Download failed', { tone: 'error' }); } finally { setDownloading(false); } }; const handleShare = async () => { try { const d = await dashApi(`/download/${jobId}?view=1`); navigator.clipboard?.writeText(d.download_url).catch(() => {}); toast('Link copied — valid 1 hour', { tone: 'success' }); } catch (e) { toast(e.message || 'Failed', { tone: 'error' }); } }; // Real stats from DynamoDB job record const realStats = job ? { job_id: job.job_id || jobId, ttw: job.ttw_count || 0, artifacts: job.artifact_count || 0, edges: job.edge_count || 0, nodes: job.node_count || 0, diverged: job.diverged_count || 0, synced: job.synced_count || 0, elapsed: job.elapsed_s || 0, size_kb: job.report_size_kb || 0, completed_at: job.completed_at || '', } : null; const scanLabel = realStats ? realStats.job_id.slice(0, 8) : 'F-8a3f'; const ttwPct = realStats ? Math.min(100, Math.round(((realStats.ttw || 0) / Math.max((realStats.ttw || 0) + (realStats.diverged || 0) + (realStats.synced || 0), 1)) * 100)) : D('ttwPct'); // totalZodb = all ZODB objects (TTW-only + diverged + synchronized). // Distinct from realStats.artifacts (filesystem artifacts in the dep graph). const totalZodb = realStats ? (realStats.ttw || 0) + (realStats.diverged || 0) + (realStats.synced || 0) : D('ttw') + D('diverged') + D('synced'); const scanDate = realStats?.completed_at ? new Date(realStats.completed_at).toISOString().slice(0, 10) : '—'; return (
); } function DashSidebar({ section, setSection, go, scanLabel, ttwPct, zodbCount }) { const sections = [ { id: 'overview', num: '§0', label: 'Overview' }, { id: 'divergence',num: '§1', label: 'Divergence', badge: `${ttwPct}%` }, { id: 'graph', num: '§2', label: 'Dependency graph' }, { id: 'coupling', num: '§3', label: 'Coupling / risk' }, { id: 'versions', num: '§4', label: 'Version groups' }, { id: 'dead', num: '§5', label: 'Dead code' }, { id: 'ai', num: '§6', label: 'Field explanations' }, { id: 'source', num: '§7', label: 'Source browser' }, { id: 'schema', num: '§8', label: 'Schema map' }, ]; return ( ); } function DashTopBar({ scanLabel, go, toast, onDownload, onShare, downloading, hasJob }) { const { signOut } = useAuth(); return (
go('reports')}>excavations / {scanLabel}
{hasJob && ( <> · )} · go('account')}>Account { signOut(); go('landing'); }}>Sign out
); } function DashContent({ section, realStats, go, toast, jobId }) { return (
{section === 'overview' && } {section === 'divergence' && } {section === 'graph' && } {section === 'coupling' && } {section === 'versions' && } {section === 'dead' && } {section === 'ai' && } {section === 'source' && } {section === 'schema' && }
); } function DemoDataNote() { return ( Detailed section data (coupling tables, version groups, source listing) shown as design reference. The full interactive report with all real data is available via Download HTML → ); } // ── §0 Overview ─────────────────────────────────────────────────────────────── function Overview({ realStats: r }) { const ttw = r?.ttw || D('ttw'); const fsArtifacts= r?.artifacts || D('total'); const edges = r?.edges || D('edges'); const diverged = r?.diverged || D('diverged'); const synced = r?.synced || D('synced'); const elapsed = r?.elapsed || D('elapsed'); const scanLabel = r ? r.job_id.slice(0, 8) : 'F-8a3f'; // ZODB objects = all objects in the live database (TTW-only + diverged + synchronized). // Distinct from fsArtifacts (scripts found in the filesystem/ZIP). const totalZodb = ttw + diverged + synced; return (
Fig. 0.1 — Section index
{[ ['§1', 'Divergence', `${ttw} of ${totalZodb} ZODB objects untracked`, 'rust'], ['§2', 'Dependency graph', `${edges} edges · ${fsArtifacts} FS artifacts`, 'slate'], ['§3', 'Coupling / risk', 'High-in-degree artifacts', 'bone'], ['§4', 'Version groups', 'Duplicate script clusters', 'slate'], ['§5', 'Dead code', 'Zero-caller candidates', 'lichen'], ['§6', 'AI explanations', 'Plain-English field notes', 'slate'], ['§7', 'Source browser', 'Full artifact index', 'slate'], ['§8', 'Schema map', 'MySQL cross-reference', 'slate'], ].map((row) => (
{row[0]} {row[1]} {row[2]}
))}
Fig. 0.2 — Scan pipeline
{[ ['Ingest ZIP', null], ['Parse ZODB', null], ['Resolve traversal', null], ['Build dep graph', null], ['Coupling analysis', null], ['AI field notes', null], ['Emit HTML', null], ].map((row, i) => (
{i + 1}. {row[0]}
))}
{ttw} scripts run in live production but exist only in the ZODB — not in version control. In event of hardware loss, these artifacts are permanently unrecoverable.
); } // §01 TTW table — exposed as window.FOSSYL_TTW_TABLE for validateDemoFixture inDeg cross-check. // Columns: [id, path, type, lastEdit, callers, risk] const TTW_TABLE = [ ['F-0418', '/portal_skins/custom/admitPatient', 'Python Script', '2014-11-22', 41, 100], ['F-0371', '/portal_skins/custom/claim_to_ledger', 'Python Script', '2012-02-17', 38, 89], ['F-0329', '/portal_skins/custom/hl7_in', 'Python Script', '2013-12-01', 27, 64], ['F-0427', '/portal_skins/custom/sendReferralFax', 'Python Script', '2011-03-08', 23, 52], ['F-0312', '/portal_skins/custom/discharge_pt', 'Page Template', '2012-08-19', 18, 44], ['F-0392', '/portal_skins/custom/ward_roster_pt', 'Page Template', '2013-06-04', 14, 35], ['F-0355', '/portal_skins/custom/printLabel', 'External Method', '2010-09-30', 12, 28], ['F-0341', '/portal_skins/custom/mergePatient', 'Python Script', '2015-04-12', 9, 23], ]; window.FOSSYL_TTW_TABLE = TTW_TABLE; // ── §1 Divergence ───────────────────────────────────────────────────────────── function Divergence({ realStats: r }) { const ttw = r?.ttw || D('ttw'); const diverged = r?.diverged || D('diverged'); const synced = r?.synced || D('synced'); const ttwPct = Math.min(100, Math.round((ttw / Math.max(ttw + diverged + synced, 1)) * 100)) || D('ttwPct'); return (
} />
Fig. 1.1 — Stratigraphic reconciliation per-folder breakdown · top 8 shown · demo data
{[ { p: '/Plone/portal_skins/custom', fs: 122, zodb: 487, ttw: 412 }, { p: '/Plone/patient_portal', fs: 304, zodb: 288, ttw: 41 }, { p: '/Plone/admission', fs: 88, zodb: 124, ttw: 68 }, { p: '/Plone/billing', fs: 140, zodb: 206, ttw: 91 }, { p: '/Plone/lab_results', fs: 67, zodb: 77, ttw: 22 }, { p: '/Plone/pharmacy', fs: 54, zodb: 89, ttw: 38 }, { p: '/Plone/portal_workflow', fs: 44, zodb: 51, ttw: 9 }, { p: '/Plone/acl_users', fs: 28, zodb: 34, ttw: 12 }, ].map((row) => )}
Fig. 1.2 — TTW-only artifacts, highest coupling first
TTW · no VCS backup showing 8 of {ttw} · demo data
{TTW_TABLE.map((r) => ( ))}
IDPathTypeLast editCallersRisk
{r[0]} {r[1]} {r[2]} {r[3]} {r[4]} open →
); } function DivRow({ p, fs, zodb, ttw }) { const matched = Math.min(fs, zodb - ttw); const orphanFs = Math.max(0, fs - matched); const total2 = matched + orphanFs + ttw; const pct = (n) => (n / total2) * 100; return (
{p}
{matched} {orphanFs} {ttw}
); } // ── §2 Dependency graph ─────────────────────────────────────────────────────── function GraphSection({ realStats: r }) { const fsArtifacts = r?.artifacts || D('total'); const ttw = r?.ttw || D('ttw'); const edges = r?.edges || D('edges'); const nodes = r?.nodes || D('total'); const diverged = r?.diverged || D('diverged'); const synced = r?.synced || D('synced'); const totalZodb = ttw + diverged + synced; const highlyCoupled = (window.COUPLING_TOP || []).filter(c => c.inDeg >= 5).length || 15; return (
Fig. 2.2 — Raw dependency graph (technical detail)
{nodes} nodes · {edges} edges · click to expand
Illustrative graph — download the HTML report for the real interactive graph.
layout
Force Radial Matrix
); } function GraphToggle({ val, children }) { const [current, setCurrent] = useState(window.__graph || 'force'); useEffect(() => { const onChange = () => setCurrent(window.__graph || 'force'); window.addEventListener('graph-change', onChange); return () => window.removeEventListener('graph-change', onChange); }, []); const active = current === val; return ( ); } function GraphSurface() { const [current, setCurrent] = useState(window.__graph || 'force'); useEffect(() => { const onChange = () => setCurrent(window.__graph || 'force'); window.addEventListener('graph-change', onChange); return () => window.removeEventListener('graph-change', onChange); }, []); return ; } // ── §3 Coupling ─────────────────────────────────────────────────────────────── // risk = W.in·in + W.ch·churn + W.ttw·ttw (ttw=1 for through-the-web only), normalized 0–100. // Rows sorted descending by raw score. Weights exposed for validateDemoFixture cross-check. (function buildCouplingRows() { const W = { in: 0.6, ch: 0.2, ttw: 0.2 }; window.FOSSYL_RISK_WEIGHTS = W; // Formula label generated from W so validator can assert label === constants window.FOSSYL_RISK_FORMULA = `risk = ${W.in}·in + ${W.ch}·churn + ${W.ttw}·ttw, norm. to 100`; const raw = [ { id: 'F-0418', n: 'admitPatient', in: 41, out: 14, ch: 18, ttw: 1 }, { id: 'F-0371', n: 'claim_to_ledger', in: 38, out: 22, ch: 12, ttw: 1 }, { id: 'F-0285', n: 'triage_rules', in: 29, out: 11, ch: 14, ttw: 0 }, { id: 'F-0329', n: 'hl7_in', in: 27, out: 16, ch: 9, ttw: 1 }, { id: 'F-0427', n: 'sendReferralFax', in: 23, out: 8, ch: 4, ttw: 1 }, { id: 'F-0312', n: 'discharge_pt', in: 18, out: 9, ch: 8, ttw: 1 }, { id: 'F-0392', n: 'ward_roster_pt', in: 14, out: 7, ch: 6, ttw: 1 }, { id: 'F-0341', n: 'mergePatient', in: 9, out: 13, ch: 5, ttw: 1 }, ]; const score = r => W.in*r.in + W.ch*r.ch + W.ttw*r.ttw; const maxRaw = Math.max(...raw.map(score)); const rows = raw .map(r => ({ ...r, risk: Math.round(score(r) / maxRaw * 100) })) .sort((a, b) => score(b) - score(a)); window.FOSSYL_COUPLING_ROWS = rows; })(); function Coupling() { const files = window.FOSSYL_COUPLING_ROWS || []; const maxIn = files.length ? files.reduce((m, f) => Math.max(m, f.in), 0) : 1; return (
Fig. 3.1 — Top coupling risk · demo data {window.FOSSYL_RISK_FORMULA || 'risk = 0.6·in + 0.2·churn + 0.2·ttw, norm. to 100'}
{files.map((f) => ( ))}
IDArtifactIn-degChurnTTWRiskIn-deg bar
{f.id} {f.n} {f.in} {f.ch} {f.ttw ? '✓' : '—'}
); } // ── §4 Versions ─────────────────────────────────────────────────────────────── // sims: per-copy similarity, all in (0.86, 0.999] (threshold ≥ 0.86, never ≥ 1.0). // Header range derived from actual min/max of each group's sims. // Exposed as window.FOSSYL_VERSION_GROUPS for validateDemoFixture. const VERSION_GROUPS = [ { id: 'V-01', n: 'send_mail', copies: 5, paths: ['portal_skins/custom/sendMail', 'billing/sendMail_v2', 'admission/sendMail', 'pharmacy/sendMail_legacy', 'shared/mailer_patch'], sims: [0.97, 0.94, 0.91, 0.88, 0.86] }, { id: 'V-02', n: 'patient_lookup', copies: 4, paths: ['patient_portal/lookup', 'admission/lookup_v2', 'lab_results/lookup', 'pharmacy/lookup'], sims: [0.96, 0.92, 0.89, 0.87] }, { id: 'V-03', n: 'format_date', copies: 7, paths: ['utils/format_date', 'patient_portal/fmt', 'billing/fmt_date', 'lab_results/dateFmt', 'admission/dateFmt', 'discharge/dateFmt', 'archive/fmt_date'], sims: [0.99, 0.96, 0.93, 0.91, 0.89, 0.87, 0.86] }, ]; window.FOSSYL_VERSION_GROUPS = VERSION_GROUPS; function Versions() { return (
{VERSION_GROUPS.map((g) => { const minSim = Math.min(...g.sims).toFixed(2); const maxSim = Math.max(...g.sims).toFixed(2); return (
{g.id} {g.n} {g.copies} copies
similarity {minSim}–{maxSim}
{g.paths.map((p, i) => (
0 ? '1px solid var(--line)' : 'none' }}>
copy_{i + 1} /{p}
{g.sims[i].toFixed(2)}
))}
); })}
); } // ── §5 Dead code ────────────────────────────────────────────────────────────── const DEAD_CANDIDATES = [ { id: 'F-1201', path: '/portal_skins/custom/old_admit_v1', type: 'Python Script', last: '2009-04-18', kb: 12.4 }, { id: 'F-1177', path: '/portal_skins/custom/print_v1', type: 'Python Script', last: '2010-02-04', kb: 8.1 }, { id: 'F-1102', path: '/portal_skins/custom/legacy_fax', type: 'External Method', last: '2011-08-22', kb: 5.7 }, { id: 'F-1044', path: '/portal_skins/custom/test_harness', type: 'Python Script', last: '2012-01-11', kb: 11.2 }, { id: 'F-0988', path: '/portal_skins/custom/old_ward_view', type: 'Page Template', last: '2012-06-17', kb: 9.8 }, ]; function DeadCode() { const totalKb = D('deathKb'); // ~187 KB estimated for all 23 candidates const totalMb = (totalKb / 1024).toFixed(2); const candCount = 23; return (
Fig. 5.1 — Candidates · demo datashowing {DEAD_CANDIDATES.length} of {candCount}
{DEAD_CANDIDATES.map((r) => ( ))}
IDPathTypeLast editSizeCallers
{r.id} {r.path} {r.type} {r.last} {r.kb} KB 0
); } // ── §6 AI explanations ──────────────────────────────────────────────────────── function AIExplanations({ jobId }) { const { toast } = useToast(); const [items, setItems] = useState(null); // null = loading, [] = no data const [loading, setLoading] = useState(true); useEffect(() => { if (!jobId) { setLoading(false); setItems([]); return; } dashApi(`/download/${jobId}?ai=1`) .then(d => { // Guard: Lambda returns {"ai_url":...} for ?ai=1. If ai_url is absent // (e.g. Lambda returned HTML download_url instead), fetch(undefined) would // silently resolve to fetch("undefined") → getfossyl.dev/undefined 404. if (!d.ai_url) throw new Error('No AI URL returned — scan may not have AI enabled'); return fetch(d.ai_url); }) .then(r => { if (!r.ok) throw new Error(`AI JSON fetch failed: ${r.status}`); return r.json(); }) .then(data => { setItems(data.explanations || []); setLoading(false); }) .catch(() => { setItems([]); setLoading(false); }); }, [jobId]); return (
{/* FOSSYL_AI_KICKER_COPY — payload description comes from window.FOSSYL_SIGNATURES_PAYLOAD_DESC (app.jsx). Copy-drift validator in graph.jsx asserts both strings reference the same field-type tokens. */} {(window.FOSSYL_AI_KICKER_COPY = "Signatures only — " + (window.FOSSYL_SIGNATURES_PAYLOAD_DESC||"") + " — are sent to Anthropic's external API at api.anthropic.com (claude-sonnet-4-6). Implementation bodies are never transmitted. Fossyl retains nothing after report generation. Tone is deliberately dry; verify before acting.") && null} {loading && (
Loading field notes…
)} {!loading && items && items.length > 0 && (
{items.map((item, i) => (
{item.path && {item.path.split('/').pop()}} {item.name || '—'}{item.artifact_type ? ` — ${item.artifact_type}` : ''}
{item.risk > 0 && 70 ? 'risk' : item.risk > 40 ? 'warn' : 'safe'}`}>risk {item.risk}}
{!item.summary && !item.explanation && !item.business_role && !item.data_flow && !item.change_risk && !item.plone_migration && (

No analysis returned — artifact may be a built-in Plone component or had insufficient source context for the model to analyse.

)} {item.summary &&

{item.summary}

} {item.explanation && !item.summary &&

{item.explanation}

} {item.business_role && (
Business role

{item.business_role}

)} {item.data_flow && (
Data flow

{item.data_flow}

)} {item.change_risk && (
Change risk

{item.change_risk}

)} {item.warning && !item.change_risk && (

Warning. {item.warning}

)} {item.plone_migration && (
Plone 6 migration

{item.plone_migration}

)}
))}
)} {!loading && (!items || items.length === 0) && (
No AI explanations for this scan

This excavation was run with AI explanations disabled, or was completed before this feature was available. Re-run with AI plain-English explanations enabled to generate field notes.

)}
); } // ── §7 Source browser ───────────────────────────────────────────────────────── function SourceBrowser({ jobId, go, toast }) { const sample = `## Python Script: sendReferralFax ## Parameters: patient_id, referral_type, recipient_fax from Products.CMFCore.utils import getToolByName patient = context.patient_portal.get(patient_id) if patient is None: raise KeyError(patient_id) pdf = context.pdf_template.render(patient, referral_type) gateway = context.portal_properties.fax_gateway gateway.dispatch(recipient_fax, pdf) ledger = context.billing.claim_to_ledger ledger(patient_id=patient_id, event='referral_fax', amount=context.portal_properties.fax_fee, ref=recipient_fax) return {'ok': True, 'timestamp': DateTime()}`; return (
{[['F-0427', 'sendReferralFax', 'Python Script', 52], ['F-0418', 'admitPatient', 'Python Script', 100], ['F-0392', 'ward_roster_pt', 'Page Template', 35]].map((f, i) => (
{f[1]}TTW
{f[0]} · {f[2]}
))}
F-0427sendReferralFaxTTW · risk 52
{sample}
); } // ── §8 Schema map ───────────────────────────────────────────────────────────── function SchemaSection({ realStats }) { // Only show real data if schema was attached to this scan. // We detect this by checking if the job has schema stats — for now we // always show the "no schema" state since schema is not yet wired to DynamoDB. // When a SQL file IS attached, docker_worker.py will write schema_table_count > 0. const hasSchema = realStats?.schema_table_count > 0; return (
{!hasSchema && (
No schema attached to this scan

Attach a phpMyAdmin structure-only SQL export on the upload page to generate the schema cross-reference map. This enables §8 and adds cross-links between Zope artifacts and database tables.

)}
); } Object.assign(window, { Dashboard }); // Run after dashboard.jsx is fully initialized — all window globals (FOSSYL_RISK_WEIGHTS, // FOSSYL_COUPLING_ROWS, FOSSYL_TTW_TABLE, FOSSYL_VERSION_GROUPS) are set by this point. if (window.validateDemoFixture) window.validateDemoFixture();