arborist-viz/arborist_viz/static/viz/web-components.js
russell@unturf.com 0cd96ab1d7
Initial commit: arborist VIZ — Merkle command center
Pyramid web app for inspecting arborist Merkle trees over sharded SQLite:
document roots, inclusion proofs, an interactive 3D Merkle lattice
(six.js / WebGL with graceful fallback), audit-chain linkage, and a live
SSE event stream.

Generated artifacts (env/, vendored static/six/ bundles, demo databases)
are gitignored and rebuilt via the Makefile (venv, six, init-db, serve).
2026-05-28 09:22:59 -04:00

221 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Phase 8 — embeddable web components.
//
// Vanilla `customElements` — no React, no build step. Drop into any host
// page that can reach this VIZ instance:
//
// <script type="module" src="https://viz.example/static/viz/web-components.js"></script>
// <arborist-proof-card src="https://viz.example" root="abc…" leaf="0"></arborist-proof-card>
// <arborist-root-viewer src="https://viz.example" root="abc…"></arborist-root-viewer>
// <arborist-claim-card src="https://viz.example" claim="claim:xyz"></arborist-claim-card>
// <arborist-run-dag src="https://viz.example" run="run:42"></arborist-run-dag>
// <arborist-trace-viewer src="https://viz.example" trace="t:1"></arborist-trace-viewer>
//
// Every card carries its own `source_roots` footer (§19.3 transparency).
const CSS = `
:host { display: block; font-family: ui-monospace, monospace; color: #d6e0ec;
background: #11161c; border: 1px solid #1f2934; border-radius: 6px;
padding: 0.6rem 0.8rem; font-size: 12px; }
.head { color: #4dd1c1; font-size: 11px; text-transform: uppercase;
letter-spacing: 0.08em; margin-bottom: 0.4rem; }
.row { display: grid; grid-template-columns: 7rem 1fr; gap: 0.5rem;
border-bottom: 1px solid #1f2934; padding: 0.15rem 0; min-width: 0; }
.row .k { color: #7a8ea3; }
/* Atomic identifier — Mode A (one-line scroll). See docs/style-guide.md §4.4. */
.row .v.mono { white-space: pre; overflow-x: auto; word-break: normal;
overflow-wrap: normal; min-width: 0; }
.pass { color: #4dd1c1; font-weight: 700; }
.fail { color: #e96b6b; font-weight: 700; }
.foot { color: #5e7081; font-size: 10.5px; margin-top: 0.4rem; }
.muted { color: #5e7081; }
.err { color: #e96b6b; }
`;
async function fetchJSON(base, path) {
const url = (base || "").replace(/\/$/, "") + path;
const res = await fetch(url);
const j = await res.json().catch(() => ({}));
if (!res.ok && !j.error) j.error = `HTTP ${res.status}`;
return j;
}
function shadow(host) {
const root = host.attachShadow({ mode: "open" });
const style = document.createElement("style");
style.textContent = CSS;
root.appendChild(style);
const body = document.createElement("div");
root.appendChild(body);
return body;
}
function row(k, v, mono = false) {
return `<div class="row"><span class="k">${k}</span><span class="v${mono ? " mono" : ""}">${v}</span></div>`;
}
// ---------- <arborist-proof-card> ---------------------------------
class ArboristProofCard extends HTMLElement {
static get observedAttributes() { return ["src", "root", "leaf"]; }
connectedCallback() { this._render(); }
attributeChangedCallback() { if (this.isConnected) this._render(); }
async _render() {
if (!this._body) this._body = shadow(this);
const src = this.getAttribute("src") || "";
const root = this.getAttribute("root");
const leaf = this.getAttribute("leaf") ?? "0";
if (!root) {
this._body.innerHTML = `<div class="err">missing "root" attribute</div>`;
return;
}
this._body.innerHTML = `<div class="head">[arborist-proof-card]</div><div class="muted">computing…</div>`;
const proof = await fetchJSON(src, `/api/proof/${root}/${leaf}`);
if (proof.error) {
this._body.innerHTML = `<div class="head">[arborist-proof-card]</div><div class="err">${proof.error}</div>`;
return;
}
const passCls = proof.passed ? "pass" : "fail";
this._body.innerHTML = `
<div class="head">[arborist-proof-card]</div>
<div class="${passCls}">${proof.passed ? "✓ PASSED" : "✗ FAILED"}</div>
${row("leaf #", proof.leaf_index)}
${row("leaf hash", proof.leaf_hash, true)}
${row("siblings", proof.sibling_hashes.length)}
${row("computed", proof.computed_root, true)}
${row("expected", proof.expected_root, true)}
<div class="foot">source_roots: ${proof.source_roots.join(", ")} · inclusion is integrity, not truth</div>
`;
}
}
// ---------- <arborist-root-viewer> --------------------------------
class ArboristRootViewer extends HTMLElement {
static get observedAttributes() { return ["src", "root"]; }
connectedCallback() { this._render(); }
attributeChangedCallback() { if (this.isConnected) this._render(); }
async _render() {
if (!this._body) this._body = shadow(this);
const src = this.getAttribute("src") || "";
const root = this.getAttribute("root");
if (!root) {
this._body.innerHTML = `<div class="err">missing "root" attribute</div>`;
return;
}
this._body.innerHTML = `<div class="head">[arborist-root-viewer]</div><div class="muted">loading…</div>`;
const r = await fetchJSON(src, `/api/root/${root}`);
if (r.error) {
this._body.innerHTML = `<div class="head">[arborist-root-viewer]</div><div class="err">${r.error}</div>`;
return;
}
this._body.innerHTML = `
<div class="head">[arborist-root-viewer]</div>
${row("title", r.title || "(untitled)")}
${row("uri", r.document_uri || "")}
${row("root", r.root_hash, true)}
${row("leaves", r.leaf_count)}
${row("chunking", r.chunking_version)}
${row("schema", r.schema_version)}
<div class="foot">source_roots: ${(r.source_roots || []).join(", ")}</div>
`;
}
}
// ---------- <arborist-claim-card> ---------------------------------
class ArboristClaimCard extends HTMLElement {
static get observedAttributes() { return ["src", "claim"]; }
connectedCallback() { this._render(); }
attributeChangedCallback() { if (this.isConnected) this._render(); }
async _render() {
if (!this._body) this._body = shadow(this);
const src = this.getAttribute("src") || "";
const claim = this.getAttribute("claim");
if (!claim) {
this._body.innerHTML = `<div class="err">missing "claim" attribute</div>`;
return;
}
const c = await fetchJSON(src, `/api/claim/${claim}`);
if (c.error) {
this._body.innerHTML = `<div class="head">[arborist-claim-card]</div><div class="err">${c.error}</div>`;
return;
}
this._body.innerHTML = `
<div class="head">[arborist-claim-card]</div>
${row("claim", c.claim_id, true)}
${row("warrant", c.warrant_level || "UNGROUNDED")}
${row("status", c.status || "speculative")}
<div class="foot">source_roots: ${(c.source_roots || []).join(", ") || "(none)"}</div>
`;
}
}
// ---------- <arborist-run-dag> ------------------------------------
class ArboristRunDag extends HTMLElement {
static get observedAttributes() { return ["src", "run"]; }
connectedCallback() { this._render(); }
attributeChangedCallback() { if (this.isConnected) this._render(); }
async _render() {
if (!this._body) this._body = shadow(this);
const src = this.getAttribute("src") || "";
const run = this.getAttribute("run") || "latest";
const r = await fetchJSON(src, `/api/run/${run}`);
if (r.error) {
this._body.innerHTML = `<div class="head">[arborist-run-dag]</div><div class="err">${r.error}</div>`;
return;
}
const stageList = (r.stages || []).map((s) => `<li>${s}</li>`).join("");
this._body.innerHTML = `
<div class="head">[arborist-run-dag]</div>
${row("run", r.run_id, true)}
<ol style="margin:0.4rem 0 0 1rem;padding:0;color:#8aa1b8">${stageList}</ol>
<div class="foot">source_roots: ${(r.source_roots || []).join(", ") || "(none)"}</div>
`;
}
}
// ---------- <arborist-trace-viewer> -------------------------------
class ArboristTraceViewer extends HTMLElement {
static get observedAttributes() { return ["src", "trace"]; }
connectedCallback() { this._render(); }
attributeChangedCallback() { if (this.isConnected) this._render(); }
async _render() {
if (!this._body) this._body = shadow(this);
const src = this.getAttribute("src") || "";
const trace = this.getAttribute("trace") || "(none)";
const t = await fetchJSON(src, `/api/trace/${trace}`);
// Phase 0–5 gate: arborist #000062 not landed → 501.
if (t.error === "not_implemented") {
this._body.innerHTML = `
<div class="head">[arborist-trace-viewer]</div>
<div class="muted">gated on ${t.gate}</div>
<div class="foot">${t.note || ""}</div>
`;
return;
}
if (t.error) {
this._body.innerHTML = `<div class="head">[arborist-trace-viewer]</div><div class="err">${t.error}</div>`;
return;
}
this._body.innerHTML = `
<div class="head">[arborist-trace-viewer]</div>
${row("trace", t.trace_id || trace, true)}
${row("causal", t.causal_status || "CORRELATIONAL_TRACE")}
<div class="foot">soft signals never enter the proof path</div>
`;
}
}
customElements.define("arborist-proof-card", ArboristProofCard);
customElements.define("arborist-root-viewer", ArboristRootViewer);
customElements.define("arborist-claim-card", ArboristClaimCard);
customElements.define("arborist-run-dag", ArboristRunDag);
customElements.define("arborist-trace-viewer", ArboristTraceViewer);