/**
* 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 = `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.
${esc(d.pattern_id)}
· evidence is ${esc(p.scope || "element")}-scope
· sources: ${esc((p.sources || []).join(", "))}Each entry below is a pointer for a human to assess, not a finding of breach.
` + hits.map((h) => `${esc(e.id)} — ${esc(e.requirement)}${esc(h.evidence.join(", "))} ·
via ${esc(h.patterns.join(", "))}| class | element | change |
|---|---|---|
${esc(f.pattern_id)} |
${esc(f.node_id)} |
${esc(f.text)} |
Target values are read off this screen — the sibling control, the headline price — not guessed.
${rows(mech)}` : "") + (adv.length ? `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]) => `| class | definition | sources |
|---|---|---|
${esc(p.id)}${
p.category_assignment !== "source" ? `aggregation` : ""} |
${esc(p.definition)} | ${esc(p.sources.join(", "))} |