File size: 5,973 Bytes
28ac122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Lean+Mathlib provenance — loads `data/lean_status.json`, exposes per-theorem
// badge HTML + a grouped table renderer for the Verification accordion.
// Pure browser-only: just fetches a static JSON manifest.

let _manifest = null;
let _byName = null;

export async function loadLeanManifest(url = "data/lean_status.json") {
  if (_manifest) return _manifest;
  const res = await fetch(url, { cache: "default" });
  if (!res.ok) throw new Error(`lean manifest fetch failed: ${res.status}`);
  _manifest = await res.json();
  _byName = {};
  for (const g of _manifest.groups) {
    for (const t of g.theorems) {
      t._group = g.id;
      t._url = sourceUrl(_manifest, t);
      _byName[t.name] = t;
    }
  }
  return _manifest;
}

export function getManifest() { return _manifest; }
export function getTheorem(name) { return _byName ? _byName[name] : null; }

export function sourceUrl(manifest, theorem) {
  const repo = manifest.lean_repo;
  return `${repo.url}/blob/${repo.default_branch}/${theorem.file}#L${theorem.line}`;
}

const escapeHtml = (s) => String(s ?? "").replace(/[&<>"]/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c]));
const escapeAttr = (s) => String(s ?? "").replace(/"/g, "&quot;");

export function badgeHtml(theoremName, label = "✓ Lean ↗") {
  const t = getTheorem(theoremName);
  if (!t) return "";
  return `<a href="${escapeAttr(t._url)}" target="_blank" rel="noopener" class="lean-badge" title="Lean theorem: ${escapeAttr(t.name)}${escapeAttr(t.claim)} (tactic: ${escapeAttr(t.tactic || "")})">${escapeHtml(label)}</a>`;
}

export function badgesForUiBinding(bindingKey) {
  // Returns concatenated badges for a UI binding (single string or array of names).
  const manifest = getManifest();
  if (!manifest || !manifest.ui_bindings) return "";
  const binding = manifest.ui_bindings[bindingKey];
  if (!binding) return "";
  const names = Array.isArray(binding) ? binding : [binding];
  return names.map(n => badgeHtml(n, "✓ Lean")).filter(Boolean).join(" ");
}

export function renderTheoremTable() {
  const manifest = getManifest();
  if (!manifest) return "<div class='subtle'>Lean manifest not loaded.</div>";

  const repo = manifest.lean_repo;
  const headerHtml = `
    <div class="lean-meta">
      <div><strong data-i18n="lean.meta.repo">Repo</strong>: <a href="${escapeAttr(repo.url)}" target="_blank">${escapeHtml(repo.name)}</a> @ <code>${escapeHtml(repo.commit_short)}</code></div>
      <div class="subtle"><strong data-i18n="lean.meta.build">Build</strong>: ${repo.build_jobs} jobs · ${escapeHtml(repo.lean_toolchain)} · ${repo.compile_time_seconds}s compile (warm)</div>
      <div class="subtle"><strong data-i18n="lean.meta.theorems">Theorems</strong>: ${manifest.summary.theorems_total} <span data-i18n="lean.meta.verified">verified</span> · ${manifest.summary.lean_rejected} <span data-i18n="lean.meta.rejected">rejected</span> · ${manifest.summary.skipped_sorry} <span data-i18n="lean.meta.sorry">sorry</span> · ${manifest.summary.substantive_findings} <span data-i18n="lean.meta.findings">substantive findings</span></div>
    </div>`;

  const findingsHtml = (manifest.findings && manifest.findings.length)
    ? `<details class="lean-findings" open>
        <summary><strong data-i18n="lean.findings.title">🔎 Substantive findings</strong> (${manifest.findings.length})</summary>
        ${manifest.findings.map(f => `
          <div class="lean-finding">
            <div><strong>${escapeHtml(f.id)}${escapeHtml(f.title)}</strong>
              <span class="lean-pill ${f.severity === "substantive" ? "v-deg" : ""}">${escapeHtml(f.severity)}</span></div>
            <div class="subtle" style="margin-top:0.3em;">${escapeHtml(f.summary)}</div>
            <div style="margin-top:0.3em;">
              <span class="subtle"><span data-i18n="lean.findings.detected_by">Detected by</span>:</span> ${badgeHtml(f.detected_by, f.detected_by + " ↗")}
              ${f.fixed_by && f.fixed_by.length ? ` · <span class="subtle"><span data-i18n="lean.findings.fixed_by">Fixed by</span>:</span> ${f.fixed_by.map(n => badgeHtml(n, n + " ↗")).join(" ")}` : ""}
            </div>
            <div class="subtle" style="margin-top:0.3em;"><strong data-i18n="lean.findings.recommendation">Recommendation</strong>: ${escapeHtml(f.recommendation)}</div>
          </div>
        `).join("")}
      </details>`
    : "";

  const groupsHtml = manifest.groups.map(g => `
    <details class="lean-group">
      <summary><strong>${escapeHtml(g.title)}</strong> <span class="subtle">(${g.theorems.length})</span></summary>
      <div class="lean-table-wrap">
        <table class="lean-table">
          <thead>
            <tr>
              <th data-i18n="lean.table.theorem">Theorem</th>
              <th data-i18n="lean.table.claim">Claim</th>
              <th data-i18n="lean.table.tactic">Tactic</th>
              <th data-i18n="lean.table.source">Source</th>
              <th data-i18n="lean.table.lean">Lean</th>
            </tr>
          </thead>
          <tbody>
            ${g.theorems.map(t => `
              <tr>
                <td><code>${escapeHtml(t.name)}</code></td>
                <td>${escapeHtml(t.claim)}</td>
                <td><code class="subtle">${escapeHtml(t.tactic || "")}</code></td>
                <td class="subtle">${t.source ? renderSource(t.source) : "—"}</td>
                <td>${badgeHtml(t.name, "L" + t.line + " ↗")}</td>
              </tr>`).join("")}
          </tbody>
        </table>
      </div>
    </details>`).join("");

  return `${headerHtml}${findingsHtml}<div class="lean-groups">${groupsHtml}</div>`;
}

function renderSource(src) {
  const parts = [];
  if (src.doc) parts.push(`<code>${escapeHtml(src.doc)}</code>`);
  if (src.section) parts.push(escapeHtml(src.section));
  if (src.line) parts.push(`L${src.line}`);
  if (src.label) parts.push(`<em>${escapeHtml(src.label)}</em>`);
  return parts.join(" · ");
}