napoles3d's picture
Refresh formalization assets
b72baa9 verified
Raw
History Blame Contribute Delete
28.5 kB
const state = {
registry: null,
selectedId: null,
filter: "all",
query: "",
tab: "article",
contextMode: "canonical"
};
const escapeHtml = (value) => String(value ?? "")
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
const titleCase = (value) => String(value).replaceAll("_", " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
function compactNumber(value) {
if (typeof value !== "number") return String(value ?? "");
if (value !== 0 && Math.abs(value) < 0.001) return value.toExponential(3);
return String(Number(value.toPrecision(8)));
}
function assertionDetail(assertion) {
const detail = assertion.detail;
if (typeof detail === "string") return detail;
if (detail && typeof detail === "object") {
if (typeof detail.absolute === "number") return `absolute difference = ${compactNumber(detail.absolute)}`;
if (typeof detail.observed === "number") {
const bound = detail.required ?? detail.required_maximum;
return `${compactNumber(detail.observed)} against bound ${compactNumber(bound)}`;
}
if (assertion.outcome === "satisfied") return "All declared diagnostics passed";
}
return JSON.stringify(detail ?? null);
}
function evidenceResults(evidence) {
const artifact = evidence.artifact_result || {};
if (evidence.verifier.name === "kwant_transport") {
return [
{ label: "T(+tau)", value: compactNumber(artifact.runs?.plus_tau?.transmission), tone: "normal" },
{ label: "T(-tau)", value: compactNumber(artifact.runs?.minus_tau?.transmission), tone: "normal" },
{ label: "absolute delta", value: compactNumber(artifact.claims?.transmission_even_in_tau?.absolute), tone: "normal" }
];
}
if (evidence.verifier.name === "fmm_occupancy") {
return [
{ label: "constant mean", value: compactNumber(artifact.regimes?.constant_mean?.union_bound_bad_probability), tone: "normal" },
{ label: "proportional mean", value: compactNumber(artifact.regimes?.proportional_mean?.union_bound_bad_probability), tone: "normal" },
{ label: "inconsistent hybrid", value: compactNumber(artifact.regimes?.inconsistent_hybrid?.union_bound_bad_probability), tone: "danger" }
];
}
const proof = Object.values(artifact.claims || {})[0] || {};
return [
{ label: "proof", value: proof.proof || "formal proof", tone: "normal" },
{ label: "source", value: proof.source || "bundled source", tone: "normal" },
{ label: "premises", value: "remain explicit", tone: "danger" }
];
}
function rawVerifierContext(record) {
return record.evidence.flatMap((evidence) => [
`${evidence.verifier.name} (${evidence.id})`,
...evidence.assertions.map((item) => `${item.assertion}: outcome=${item.outcome}; detail=${assertionDetail(item)}`)
]).join("\n");
}
function canonicalContext(record) {
const dimensions = Object.entries(record.status.dimensions)
.map(([name, value]) => `${titleCase(name)}: ${value}`);
const obligations = record.obligations.map((item) => `Open: ${item.message}`);
return [`Status: ${record.status.derived}`, ...dimensions, ...obligations].join("\n");
}
function normalizeCanonicalClaim(record, registry) {
const claim = record.claim.record;
const study = registry.studies.find((item) => item.id === record.study?.id);
const evidence = record.evidence.map((item) => ({
id: item.id,
verifier: item.verifier.name,
classification: item.classification,
authenticated: item.authenticated,
assertions: item.assertions.map((assertion) => ({
id: assertion.assertion,
outcome: assertion.outcome,
detail: assertionDetail(assertion)
})),
results: evidenceResults(item),
formalization: item.record?.formalization || null
}));
const isFormal = record.evidence.some((item) => item.verifier.evidence_kind === "formal_proof");
const evidenceType = !record.evidence.length ? "open" : (isFormal ? "formal" : "computational");
return {
id: record.claim.id,
study_id: record.study?.id,
study: study?.name || record.study?.record?.name || "Unassigned study",
domain: claim.topic,
role: record.claim.role || "background",
kind: claim.kind,
evidence_type: evidenceType,
title: record.claim.title,
question: record.narrative.claim_question,
plain_language_conclusion: record.narrative.plain_language_conclusion,
scope_summary: record.narrative.scope_summary,
remaining_uncertainty: record.narrative.remaining_uncertainty,
statement: record.claim.statement_text,
latex: typeof claim.statement === "object" ? claim.statement.latex : "",
status: record.status.derived,
scope: record.scope_boundary.scope,
dimensions: record.status.dimensions,
conditions: record.scope_boundary.conditions,
limitations: record.scope_boundary.limitations,
obligations: record.obligations.map((item) => item.message),
dependencies: record.dependency_closure.nodes,
evidence,
formalizations: record.formalizations || [],
raw_context: rawVerifierContext(record),
canonical_context: canonicalContext(record),
canonical_state: record
};
}
function normalizeRegistry(registry) {
if (registry.claims[0]?.claim?.record) {
const claims = registry.claims.map((record) => normalizeCanonicalClaim(record, registry));
claims.sort((left, right) => {
const featured = (claim) => claim.study_id === "twisted-ribbon" ? 0 : 1;
return featured(left) - featured(right) || left.id.localeCompare(right.id);
});
return { ...registry, claims };
}
return registry;
}
function selectedClaim() {
return state.registry.claims.find((claim) => claim.id === state.selectedId) || state.registry.claims[0];
}
function studyForClaim(claim) {
return state.registry.studies.find((study) => study.id === claim.study_id);
}
function filteredClaims() {
const needle = state.query.trim().toLowerCase();
return state.registry.claims.filter((claim) => {
const matchesType = state.filter === "all" || claim.evidence_type === state.filter;
const haystack = [claim.id, claim.study, claim.domain, claim.title, ...claim.evidence.map((item) => item.verifier)].join(" ").toLowerCase();
return matchesType && (!needle || haystack.includes(needle));
});
}
function renderMetrics() {
const claims = state.registry.claims;
document.querySelector("#study-count").textContent = new Set(claims.map((claim) => claim.study)).size;
document.querySelector("#claim-count").textContent = claims.length;
const indexed = state.registry.studies.reduce((sum, study) => sum + study.coverage.total, 0);
document.querySelector("#claim-coverage-label").textContent = `Full states of ${indexed} indexed`;
document.querySelector("#coverage-note").textContent = `${claims.length} of ${indexed} shown`;
document.querySelector("#evidence-count").textContent = claims.reduce((sum, claim) => sum + claim.evidence.length, 0);
document.querySelector("#obligation-count").textContent = claims.reduce((sum, claim) => sum + claim.obligations.length, 0);
}
function renderCatalog() {
const claims = filteredClaims();
const list = document.querySelector("#claim-list");
document.querySelector("#visible-count").textContent = `${claims.length} claim${claims.length === 1 ? "" : "s"}`;
if (!claims.length) {
list.innerHTML = '<div class="empty-state">No claims match this view.</div>';
return;
}
list.innerHTML = claims.map((claim) => `
<button class="claim-row ${claim.id === state.selectedId ? "selected" : ""}" data-claim-id="${escapeHtml(claim.id)}" type="button" role="listitem">
<div class="claim-row-top">
<span class="claim-id">${escapeHtml(claim.id)}</span>
<span class="role-badge role-${escapeHtml(claim.role)}">${escapeHtml(titleCase(claim.role))}</span>
</div>
<h3>${escapeHtml(claim.title)}</h3>
<div class="claim-row-meta">
<span>${escapeHtml(claim.study)}</span>
<span>${escapeHtml(claim.evidence[0]?.verifier || "no verifier")}</span>
</div>
</button>
`).join("");
list.querySelectorAll("[data-claim-id]").forEach((button) => {
button.addEventListener("click", () => {
state.selectedId = button.dataset.claimId;
state.tab = "article";
renderCatalog();
renderDetail();
if (window.innerWidth < 761) document.querySelector("#detail-panel").scrollIntoView({ behavior: "smooth", block: "start" });
});
});
}
function detailHeader(claim) {
const study = studyForClaim(claim);
return `
<header class="detail-header">
<p class="eyebrow">${escapeHtml(claim.domain)} / ${escapeHtml(claim.id)}</p>
<div class="detail-title-row">
<h2>${escapeHtml(claim.title)}</h2>
<span class="status-badge status-${escapeHtml(claim.status)}">${escapeHtml(claim.status)}</span>
</div>
<p class="detail-statement">${escapeHtml(claim.statement)}</p>
<section class="narrative-lead" aria-label="Plain-language claim summary">
<div><small>Question</small><p>${escapeHtml(claim.question)}</p></div>
<div><small>Resolution</small><p>${escapeHtml(claim.plain_language_conclusion)}</p></div>
</section>
<p class="coverage-disclosure">
${study.coverage.is_complete ? "Complete registry" : "Curated registry"}:
${study.coverage.shown} of ${study.coverage.total} claims have full records here.
The study graph indexes all ${study.coverage.total}.
</p>
<div class="scope-line">
<span class="type-badge">${escapeHtml(claim.kind)}</span>
<span class="type-badge">scope: ${escapeHtml(claim.scope)}</span>
<span class="type-badge">revision: ${escapeHtml(claim.dimensions.revision)}</span>
</div>
</header>
<nav class="tabs" aria-label="Claim views">
${[
["article", "Article"],
["study", "Study overview"],
["overview", "Overview"],
["formalization", "Formalization"],
["evidence", "Evidence"],
["graph", "Claim graph"],
["context", "LLM context"],
["json", "State JSON"]
].map(([id, label]) => `<button class="tab ${state.tab === id ? "active" : ""}" data-tab="${id}" type="button">${label}</button>`).join("")}
</nav>
`;
}
function equationById(study, equationId) {
return study.equations.find((equation) => equation.id === equationId);
}
function articleClaimBlock(study, claimId, selectedId) {
const index = study.claim_index.find((item) => item.id === claimId);
const record = state.registry.claims.find((item) => item.id === claimId);
if (!index) return "";
return `
<button class="article-claim ${claimId === selectedId ? "current" : ""}" data-article-claim="${escapeHtml(claimId)}" type="button">
<span class="claim-id">${escapeHtml(claimId)}</span>
<span class="article-claim-copy">
<strong>${escapeHtml(index.title)}</strong>
<small>${escapeHtml(record?.plain_language_conclusion || "Indexed claim")}</small>
</span>
<span class="status-badge status-${escapeHtml(index.status)}">${escapeHtml(index.status)}</span>
</button>
`;
}
function articleEquationBlock(study, equationId) {
const equation = equationById(study, equationId);
if (!equation) return "";
const number = study.equations.findIndex((item) => item.id === equationId) + 1;
const links = equation.correspondence || [];
return `
<figure class="equation-block">
<figcaption>
<span>Equation ${number}</span>
<span class="equation-id">${escapeHtml(equation.id)}</span>
<span class="equation-role equation-role-${escapeHtml(equation.role)}">${escapeHtml(titleCase(equation.role))}</span>
</figcaption>
<div class="math-display">\\[${escapeHtml(equation.latex)}\\]</div>
<p>${escapeHtml(equation.plain_language)}</p>
<div class="equation-links">
${equation.depends_on.length ? `<span>Uses ${equation.depends_on.map(escapeHtml).join(", ")}</span>` : "<span>Starting definition</span>"}
${links.map((item) => `<span class="correspondence correspondence-${escapeHtml(item.status)}">${escapeHtml(titleCase(item.type))}: ${escapeHtml(item.status)}</span>`).join("")}
</div>
</figure>
`;
}
function articleView(claim) {
const study = studyForClaim(claim);
const article = study.article;
if (!article) return '<div class="empty-state">This study has no ordered article yet.</div>';
return `
<section class="article-view">
<header class="article-heading">
<p class="eyebrow">Versioned scientific argument</p>
<h3>${escapeHtml(article.title)}</h3>
<p>${escapeHtml(study.research_question)}</p>
</header>
<nav class="article-outline" aria-label="Article sections">
${article.sections.map((section, index) => `<a href="#section-${escapeHtml(section.id)}"><span>${index + 1}</span>${escapeHtml(section.title)}</a>`).join("")}
</nav>
<div class="article-body">
${article.sections.map((section, index) => `
<section class="article-section" id="section-${escapeHtml(section.id)}">
<div class="section-number">${index + 1}</div>
<div class="section-content">
<h3>${escapeHtml(section.title)}</h3>
${section.blocks.map((block) => {
if (block.type === "prose") return `<p class="article-prose">${escapeHtml(block.text)}</p>`;
if (block.type === "equation") return articleEquationBlock(study, block.ref);
return articleClaimBlock(study, block.ref, claim.id);
}).join("")}
</div>
</section>
`).join("")}
</div>
<footer class="article-resolution">
<small>Current study resolution</small>
<p>${escapeHtml(study.resolution_summary)}</p>
</footer>
</section>
`;
}
function studyView(claim) {
const study = studyForClaim(claim);
return `
<section>
<div class="study-heading">
<div>
<p class="eyebrow">Research question</p>
<h3>${escapeHtml(study.research_question)}</h3>
</div>
<span class="coverage-badge">${study.coverage.shown} / ${study.coverage.total} full records</span>
</div>
<div class="study-summary-grid">
<section><h3 class="section-title">Approach</h3><p>${escapeHtml(study.approach_summary)}</p></section>
<section><h3 class="section-title">Current resolution</h3><p>${escapeHtml(study.resolution_summary)}</p></section>
</div>
<h3 class="section-title chain-title">Complete claim chain</h3>
<ol class="study-chain">
${study.claim_index.map((item) => `
<li class="${item.id === claim.id ? "current" : ""} ${item.shown ? "shown" : "indexed"}">
<div>
<span class="claim-id">${escapeHtml(item.id)}</span>
<strong>${escapeHtml(item.title)}</strong>
</div>
<div class="chain-meta">
<span class="role-badge role-${escapeHtml(item.role)}">${escapeHtml(titleCase(item.role))}</span>
<span>${item.shown ? "Full record" : "Indexed only"}</span>
</div>
</li>
`).join("")}
</ol>
<p class="graph-note">Indexed-only nodes remain visible so a curated demo cannot be mistaken for the complete scientific argument.</p>
</section>
`;
}
function overviewView(claim) {
return `
<section>
<h3 class="section-title">Epistemic dimensions</h3>
<div class="dimension-grid">
${Object.entries(claim.dimensions).map(([name, value]) => `
<div class="dimension"><small>${escapeHtml(name)}</small><strong>${escapeHtml(value)}</strong></div>
`).join("")}
</div>
<div class="two-column">
<section>
<h3 class="section-title">Scope in plain language</h3>
<p>${escapeHtml(claim.scope_summary)}</p>
</section>
<section>
<h3 class="section-title">Remaining uncertainty</h3>
<p>${escapeHtml(claim.remaining_uncertainty)}</p>
</section>
</div>
<div class="two-column">
<section>
<h3 class="section-title">Open obligations</h3>
<ul class="plain-list">${claim.obligations.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>
</section>
<section>
<h3 class="section-title">Does not establish</h3>
<ul class="plain-list limitations">${claim.limitations.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>
</section>
</div>
<div class="two-column">
<section>
<h3 class="section-title">Declared conditions</h3>
<ul class="plain-list">${claim.conditions.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</ul>
</section>
<section>
<h3 class="section-title">Formal statement</h3>
<div class="context-panel"><pre>${escapeHtml(claim.latex)}</pre></div>
</section>
</div>
</section>
`;
}
function evidenceView(claim) {
if (!claim.evidence.length) {
return '<div class="empty-state compact">No verifier evidence targets this claim revision. Its status remains declared, proposed, or conditional from dependencies.</div>';
}
return claim.evidence.map((record) => `
<section class="evidence-record">
<div class="evidence-heading">
<div><p class="eyebrow">${escapeHtml(record.id)}</p><h3>${escapeHtml(record.verifier)}</h3></div>
<span class="status-badge status-${claim.status}">${escapeHtml(record.classification)}</span>
</div>
<table class="assertion-table">
<thead><tr><th>Assertion contract</th><th>Outcome</th><th>Observed detail</th></tr></thead>
<tbody>
${record.assertions.map((item) => `<tr><td><code>${escapeHtml(item.id)}</code></td><td class="outcome-${escapeHtml(item.outcome)}">${escapeHtml(item.outcome)}</td><td>${escapeHtml(item.detail)}</td></tr>`).join("")}
</tbody>
</table>
<div class="result-strip">
${record.results.map((item) => `<div class="result-item ${item.tone === "danger" ? "danger" : ""}"><small>${escapeHtml(item.label)}</small><strong>${escapeHtml(item.value)}</strong></div>`).join("")}
</div>
<p class="context-warning">Integrity-valid record. Cryptographic authentication: ${record.authenticated ? "present" : "not present"}.</p>
</section>
`).join("");
}
function formalizationView(claim) {
if (!claim.formalizations.length) {
return '<div class="empty-state compact">No committed formalization request targets this claim revision.</div>';
}
return claim.formalizations.map((record) => {
const statement = record.formal_statement;
const approval = record.semantic_approval;
const grounding = record.scientific_grounding;
return `
<section class="formalization-record">
<header class="formalization-heading">
<div>
<p class="eyebrow">${escapeHtml(record.id)} / Lean 4</p>
<h3>${escapeHtml(statement.theorem_name)}</h3>
</div>
<div class="formalization-badges">
<span class="type-badge">${escapeHtml(record.status)}</span>
<span class="grounding grounding-${escapeHtml(grounding.status)}">grounding: ${escapeHtml(grounding.status)}</span>
</div>
</header>
<p class="formalization-summary">${escapeHtml(record.summary)}</p>
<div class="formal-statement"><pre>${escapeHtml(statement.declaration)}</pre></div>
<section>
<h3 class="section-title">Semantic mapping</h3>
<table class="mapping-table">
<thead><tr><th>Scientific source</th><th>Lean target</th><th>Mapping</th></tr></thead>
<tbody>
${record.semantic_mapping.map((item) => `
<tr>
<td>${escapeHtml(item.source)}</td>
<td><code>${escapeHtml(item.target)}</code></td>
<td><span class="mapping-status mapping-${escapeHtml(item.status)}">${escapeHtml(item.status)}</span></td>
</tr>
`).join("")}
</tbody>
</table>
</section>
<div class="two-column formalization-columns">
<section>
<h3 class="section-title">Lean assumptions</h3>
<ul class="plain-list">${record.assumptions.map((item) => `<li>${escapeHtml(item)}</li>`).join("") || "<li>None recorded</li>"}</ul>
</section>
<section>
<h3 class="section-title">Outside the proof</h3>
<ul class="plain-list limitations">${record.unformalized.map((item) => `<li>${escapeHtml(item)}</li>`).join("") || "<li>None recorded</li>"}</ul>
</section>
</div>
<div class="grounding-note">
<small>Scientific grounding</small>
<p>${escapeHtml(grounding.rationale)}</p>
</div>
<footer class="formalization-footer">
<span>Semantic approval: <strong>${escapeHtml(approval.status)}</strong>${approval.approved_by ? ` by ${escapeHtml(approval.approved_by)}` : ""}</span>
<span>Statement digest: <code>${escapeHtml(statement.sha256.slice(0, 12))}</code></span>
</footer>
</section>
`;
}).join("");
}
function graphView(claim) {
const nodes = [...claim.dependencies, { id: claim.id, kind: claim.kind, status: claim.status, title: claim.title, root: true }];
return `
<section>
<h3 class="section-title">Transitive dependency path</h3>
<div class="graph" aria-label="Claim dependency graph">
${nodes.map((node, index) => `
${index ? '<div class="graph-arrow" aria-hidden="true">→</div>' : ""}
<div class="graph-node ${node.kind === "assumption" ? "assumption" : ""} ${node.root ? "root" : ""}">
<small>${escapeHtml(node.id)} / ${escapeHtml(node.kind)}</small>
<strong>${escapeHtml(node.title)}</strong>
<span class="status-badge status-${escapeHtml(node.status)}">${escapeHtml(node.status)}</span>
</div>
`).join("")}
</div>
<p class="graph-note">A successful child does not erase unresolved premises. Conditional status propagates through the graph, and changing a locked dependency makes descendants stale.</p>
</section>
`;
}
function contextView(claim) {
const study = studyForClaim(claim);
const mathematicalSequence = study.equations.map((equation, index) => [
`Equation ${index + 1} (${equation.id}, ${equation.role}): ${equation.latex.trim()}`,
`Meaning: ${equation.plain_language.trim()}`,
`Linked claims: ${equation.claim_ids.join(", ") || "none"}`,
`Correspondence: ${(equation.correspondence || []).map((item) => `${item.type}=${item.status}`).join(", ") || "none"}`
].join("\n")).join("\n\n");
const formalizationSequence = claim.formalizations.map((item) => [
`Formalization ${item.id}: status=${item.status}`,
`Lean theorem: ${item.formal_statement.declaration.trim()}`,
`Scientific grounding: ${item.scientific_grounding.status}`,
`Grounding rationale: ${item.scientific_grounding.rationale}`,
`Unformalized: ${item.unformalized.join("; ") || "none"}`
].join("\n")).join("\n\n");
const narrative = [
`Study question: ${study.research_question}`,
`Study resolution: ${study.resolution_summary}`,
`Claim question: ${claim.question}`,
`Resolution: ${claim.plain_language_conclusion}`,
`Scope: ${claim.scope_summary}`,
`Still open: ${claim.remaining_uncertainty}`,
"",
"Ordered mathematical sequence:",
mathematicalSequence,
"",
"Formalization state:",
formalizationSequence || "No formalization requested",
"",
claim.canonical_context
].join("\n");
const content = state.contextMode === "canonical" ? narrative : claim.raw_context;
const warning = state.contextMode === "raw"
? "Raw outputs make passes=true easy to overinterpret. The reader must reconstruct scope and dependencies."
: "Canonical state separates proof, corroboration, assumptions, scope, and provenance before an LLM interprets the claim.";
return `
<section>
<h3 class="section-title">Same scientific record, different context</h3>
<div class="context-toggle">
<button class="${state.contextMode === "raw" ? "active" : ""}" data-context="raw" type="button">Raw verifier output</button>
<button class="${state.contextMode === "canonical" ? "active" : ""}" data-context="canonical" type="button">GitScience state</button>
</div>
<div class="context-panel"><pre>${escapeHtml(content)}</pre></div>
<p class="context-warning">${escapeHtml(warning)}</p>
<div class="two-column">
<section><h3 class="section-title">Evaluation question</h3><p>What is established, what remains assumed, and which conclusion would be an overgeneralization?</p></section>
<section><h3 class="section-title">Proposed benchmark</h3><p>Compare small and large models on fact recall, false generalization, requested controls, token use, and confidence.</p></section>
</div>
</section>
`;
}
function jsonView(claim) {
const value = { study: studyForClaim(claim), claim_state: claim.canonical_state || claim };
return `
<section>
<div class="json-toolbar"><button class="button secondary" id="copy-json" type="button">Copy JSON</button></div>
<div class="json-panel"><pre>${escapeHtml(JSON.stringify(value, null, 2))}</pre></div>
</section>
`;
}
function renderDetail() {
const claim = selectedClaim();
state.selectedId = claim.id;
const views = { article: articleView, study: studyView, overview: overviewView, formalization: formalizationView, evidence: evidenceView, graph: graphView, context: contextView, json: jsonView };
const panel = document.querySelector("#detail-panel");
panel.innerHTML = `${detailHeader(claim)}<div class="tab-content">${views[state.tab](claim)}</div>`;
panel.querySelectorAll("[data-tab]").forEach((button) => button.addEventListener("click", () => {
state.tab = button.dataset.tab;
renderDetail();
}));
panel.querySelectorAll("[data-context]").forEach((button) => button.addEventListener("click", () => {
state.contextMode = button.dataset.context;
renderDetail();
}));
panel.querySelectorAll("[data-article-claim]").forEach((button) => button.addEventListener("click", () => {
state.selectedId = button.dataset.articleClaim;
renderCatalog();
renderDetail();
}));
const copyButton = panel.querySelector("#copy-json");
if (copyButton) copyButton.addEventListener("click", async () => {
const value = { study: studyForClaim(claim), claim_state: claim.canonical_state || claim };
await navigator.clipboard.writeText(JSON.stringify(value, null, 2));
copyButton.textContent = "Copied";
window.setTimeout(() => { copyButton.textContent = "Copy JSON"; }, 1200);
});
if (window.MathJax?.typesetPromise) {
window.MathJax.typesetClear?.([panel]);
window.MathJax.typesetPromise([panel]).catch(() => {});
}
}
function bindControls() {
document.querySelector("#search-input").addEventListener("input", (event) => {
state.query = event.target.value;
renderCatalog();
});
document.querySelectorAll("[data-filter]").forEach((button) => button.addEventListener("click", () => {
state.filter = button.dataset.filter;
document.querySelectorAll("[data-filter]").forEach((item) => item.classList.toggle("active", item === button));
renderCatalog();
}));
const dialog = document.querySelector("#submit-dialog");
document.querySelector("#submit-button").addEventListener("click", () => dialog.showModal());
}
async function init() {
bindControls();
try {
const response = await fetch("claims.json?v=2");
if (!response.ok) throw new Error(`Registry request failed: ${response.status}`);
state.registry = normalizeRegistry(await response.json());
state.selectedId = state.registry.claims.find((claim) => claim.id === "GS-QT-0005")?.id || state.registry.claims[0].id;
renderMetrics();
renderCatalog();
renderDetail();
} catch (error) {
document.querySelector("#detail-panel").innerHTML = `<div class="empty-state">Could not load the demo registry.<br>${escapeHtml(error.message)}</div>`;
}
}
init();