/** * Static Space front-end. Wires the browser Sludge to the page. * * Everything runs client-side: the pasted interface never leaves the machine. That is a * deliberate property for this tool — people paste screens from products they are reviewing. */ import { SludgeWeb, treeFromHtml } from "./sludge.js"; import { contrastRatio } from "./featurizer.js"; const $ = (id) => document.getElementById(id); const PROFILE_MIN = { triage: 0.25, review: 0.5, audit: 0.75 }; const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); const S = new SludgeWeb(); let lastReport = null; function setStatus(msg, pct = null) { $("status").textContent = msg; if (pct !== null) $("bar").style.width = `${Math.round(pct * 100)}%`; } // ---- tabs ------------------------------------------------------------------------------------ document.querySelectorAll('[role="tab"]').forEach((b) => { b.addEventListener("click", () => { document.querySelectorAll('[role="tab"]').forEach((x) => { x.setAttribute("aria-selected", String(x === b)); }); document.querySelectorAll('[role="tabpanel"]').forEach((p) => { p.classList.toggle("hidden", p.id !== b.dataset.panel); }); }); }); // ---- boot ------------------------------------------------------------------------------------ (async () => { try { let step = 0; await S.load("assets", (m, frac) => setStatus(m, frac === null || frac === undefined ? Math.min(0.9, ++step / 6) : frac)); setStatus("ready — the model is loaded and runs locally, cached for next time", 1); const juris = $("juris"); juris.innerHTML = Object.entries(S.provisions) .map(([k, v]) => ``) .join(""); const templates = [...new Set(S.examples.map((e) => e.template))].sort(); $("tpl").innerHTML = templates.map((t) => ``).join(""); const syncPatterns = () => { const t = $("tpl").value; const pats = S.examples.filter((e) => e.template === t).map((e) => e.pattern); $("pat").innerHTML = pats.map((p) => ``).join(""); }; $("tpl").addEventListener("change", syncPatterns); syncPatterns(); $("about").innerHTML = `Model: ${esc(S.meta.model.preset)}, ` + `${(S.meta.model.parameters / 1e6).toFixed(0)}M parameters, ` + `${S.meta.pattern_ids.length} classes from published taxonomies · ` + `model · ` + `dataset`; renderTaxonomy(); $("run").disabled = false; } catch (e) { setStatus(`could not start: ${e.message}`); console.error(e); } })(); // ---- example loading --------------------------------------------------------------------------- $("load").addEventListener("click", () => { const ex = S.examples.find( (e) => e.template === $("tpl").value && e.pattern === $("pat").value); if (!ex) return; $("input").value = JSON.stringify(ex.unfair, null, 2); const diffs = ex.diffs.map( (d) => `${esc(d.node_id)} · ${esc(d.field)}: ` + `${esc(String(d.before).slice(0, 34))} → ${esc(String(d.after).slice(0, 34))}`); $("exnote").innerHTML = `The tree above is a fair screen with exactly one change ` + `(${esc(ex.edit_scope)}):
${diffs.join("
")}
` + `Ground-truth evidence: ${esc(ex.evidence.join(", "))}`; }); // ---- analysis ------------------------------------------------------------------------------------ $("run").addEventListener("click", async () => { const raw = $("input").value.trim(); if (!raw) { setStatus("paste a UI tree or an HTML fragment first"); return; } let tree; try { tree = raw.startsWith("{") ? JSON.parse(raw) : treeFromHtml(raw); } catch (e) { setStatus(`could not parse that input: ${e.message}`); return; } if (!tree.nodes || !tree.nodes.length) { setStatus("no elements found in that input"); return; } $("run").disabled = true; setStatus("analysing…"); try { const pred = await S.predict(tree); const minScore = PROFILE_MIN[$("profile").value]; const dets = S.decode(pred, minScore); const norm = S.fz.normalise(tree); const hits = S.mapProvisions(dets, $("juris").value); const fixes = S.remediate(norm, dets); lastReport = { determination: "none - Sludge does not determine legality", disclaimer: S.meta.disclaimer, profile: $("profile").value, jurisdiction: $("juris").value, ms: Math.round(pred.ms * 100) / 100, nodes: norm.nodes.length, screens: norm.screens.length, detections: dets, findings: hits, remediation: fixes, }; renderFindings(norm, dets, fixes); renderProvisions(hits); renderFixes(fixes, norm); $("p-json").innerHTML = `
${esc(JSON.stringify(lastReport, null, 2))}
`; setStatus(`${dets.length} pattern(s) across ${norm.nodes.length} elements on ` + `${norm.screens.length} screen(s) — model forward ${pred.ms.toFixed(1)} ms`); } catch (e) { setStatus(`analysis failed: ${e.message}`); console.error(e); } finally { $("run").disabled = false; } }); // ---- rendering ------------------------------------------------------------------------------- function renderFindings(tree, dets, fixes) { const el = $("p-find"); if (!dets.length) { el.innerHTML = `
No patterns crossed this profile's threshold.

That is not a clean bill of health: it means nothing crossed the threshold this profile fixes. A stricter profile, a different jurisdiction table, or a human may reach a different view.

`; return; } const byId = new Map(tree.nodes.map((n) => [n.node_id, n])); const fixOf = new Map(fixes.map((f) => [f.pattern_id, f])); el.innerHTML = dets.map((d) => { const p = S.patterns.get(d.pattern_id) || {}; const ev = d.evidence.map((nid) => { const n = byId.get(nid); if (!n) return ""; const c = contrastRatio(n.color, n.background); const lbl = (n.aria_label || n.text || "").slice(0, 70); return `
${esc(nid)} <${esc(n.role)}> ${lbl ? `“${esc(lbl)}”` : ""}
screen ${n.screen} · ${n.bbox[2].toFixed(0)}×${n.bbox[3].toFixed(0)} px · ${n.font_size.toFixed(0)} px type · contrast ${c.toFixed(2)}:1${ n.default_selected ? " · selected by default" : ""}${ n.disabled ? " · disabled" : ""}
`; }).join(""); const fx = fixOf.get(d.pattern_id); return `
${d.score.toFixed(2)}

${esc(p.display || d.pattern_id)}

${esc(p.category || "")} · ${esc(d.pattern_id)} · evidence is ${esc(p.scope || "element")}-scope · sources: ${esc((p.sources || []).join(", "))}
${esc(p.definition || "")}
${ev} ${fx ? `
${ fx.mechanical ? "measured fix" : "advisory — needs new copy"}
${esc(fx.text)}
` : ""}
`; }).join(""); } function renderProvisions(hits) { const el = $("p-prov"); if (!hits.length) { el.innerHTML = `
No provisions in this table are reached by the detected classes.
`; return; } el.innerHTML = `

Each entry below is a pointer for a human to assess, not a finding of breach.

` + hits.map((h) => `
${esc(h.citation)} — ${esc(h.title)}${ h.status !== "in_force" ? `${esc(h.status)}` : ""}
${esc(h.instrument)}
${h.scope_note ? `
Scope: ${esc(h.scope_note)}
` : ""} ${h.status_note ? `
${esc(h.status_note)}
` : ""}
Evidence: ${esc(h.evidence.join(", "))} · via ${esc(h.patterns.join(", "))}
`).join(""); } function renderFixes(fixes, tree) { const el = $("p-fix"); if (!fixes.length) { el.innerHTML = `
Nothing to change.
`; return; } const mech = fixes.filter((f) => f.mechanical); const adv = fixes.filter((f) => !f.mechanical); const rows = (list) => `${list.map((f) => ``).join("")}
classelementchange
${esc(f.pattern_id)} ${esc(f.node_id)} ${esc(f.text)}
`; el.innerHTML = (mech.length ? `

Measured changes

Target values are read off this screen — the sibling control, the headline price — not guessed.

${rows(mech)}` : "") + (adv.length ? `

Advisory

These need new copy or a new element, so no target value can be measured from the current screen.

${rows(adv)}` : ""); } function renderTaxonomy() { const byCat = new Map(); for (const p of S.meta.patterns) { if (!byCat.has(p.category)) byCat.set(p.category, []); byCat.get(p.category).push(p); } $("p-tax").innerHTML = `

${byCat.size} categories / ${S.meta.patterns.length} classes, all from published work. Entries marked aggregation are this project's own placement decision, not a claim from the cited work.

` + [...byCat].map(([cat, ps]) => `

${esc(cat.replace(/_/g, " "))}

${ps.map((p) => ``).join("")}
classdefinitionsources
${esc(p.id)}${ p.category_assignment !== "source" ? `aggregation` : ""} ${esc(p.definition)} ${esc(p.sources.join(", "))}
`).join(""); }