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).
221 lines
8.8 KiB
JavaScript
221 lines
8.8 KiB
JavaScript
// 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);
|