Sunstead trust scoring project
1// Tangled-native overlay (PRD 7.4). UI only: it reads the same /score API the
2// dashboard does and injects a "trust hat" pill next to contributor DIDs on
3// tangled.org. The brain stays in the service; this never touches DuckDB.
4//
5// ponytail: vanilla content script, no build step (same call as the built-in
6// pages) over the Bun/oxlint/zod toolchain. CONFIRM the DID selector against the
7// real tangled.org DOM — like the NSIDs, this is a best-guess until verified.
8
9const API = "http://127.0.0.1:8000";
10const DID_RE = /did:(?:plc:[a-z2-7]+|web:[a-z0-9.\-]+)/gi;
11const COLOR = { fast_lane: "#1a7f37", normal_queue: "#9a6700", needs_human: "#cf222e" };
12
13const cache = new Map();
14
15async function score(did) {
16 if (cache.has(did)) return cache.get(did);
17 const p = fetch(`${API}/score/${encodeURIComponent(did)}`)
18 .then((r) => (r.ok ? r.json() : null))
19 .catch(() => null); // service down -> render nothing, never break the page
20 cache.set(did, p);
21 return p;
22}
23
24function pill(s) {
25 const el = document.createElement("span");
26 el.className = "tangled-trust-hat";
27 el.dataset.did = s.did;
28 el.textContent = ` ${Math.round((s.calibrated_prob ?? 0) * 100)}% `;
29 const factors = (s.explanation?.top_factors || []).join("\n");
30 el.title = `${s.decision}\n${factors}`;
31 Object.assign(el.style, {
32 background: COLOR[s.decision] || "#57606a",
33 color: "#fff", borderRadius: "999px", padding: "1px 7px",
34 fontSize: "11px", fontWeight: "600", marginLeft: "6px", verticalAlign: "middle",
35 });
36 return el;
37}
38
39// Find text-node occurrences of a DID and tag their parent once.
40async function scan() {
41 const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
42 const hits = [];
43 for (let n = walker.nextNode(); n; n = walker.nextNode()) {
44 const m = n.nodeValue.match(DID_RE);
45 if (m) hits.push({ node: n, dids: [...new Set(m)] });
46 }
47 for (const { node, dids } of hits) {
48 const host = node.parentElement;
49 if (!host || host.querySelector(":scope > .tangled-trust-hat")) continue;
50 for (const did of dids) {
51 const s = await score(did);
52 if (s) host.appendChild(pill(s));
53 }
54 }
55}
56
57scan();
58// tangled.org is an SPA; re-scan on DOM changes (debounced).
59let t;
60new MutationObserver(() => {
61 clearTimeout(t);
62 t = setTimeout(scan, 400);
63}).observe(document.body, { childList: true, subtree: true });