Spaces:
Running
Running
deploy(hf): sync szl-holdings/a11oy@ac02db5a10e5b3d4c9b2260674111ff2335888bf derived COPY set
55e5019 verified | <!-- | |
| a11oy Command Cockpit — a single-screen LIVE health cockpit for the whole | |
| organism. 0 runtime CDN, 0 build step, pure inline CSS/JS (doctrine: no | |
| fabricated data). It reads ONE real aggregate feed: | |
| GET /api/a11oy/v1/engine/status (schema: szl.engine_status/v1) | |
| and renders the mind, the six organs, energy and swarm exactly as the server | |
| reports them. Every value carries an honesty state: LIVE (reachable/ok), | |
| DOWN (reachable=false), or SAMPLE/— when the server itself returns null. No | |
| number is invented client-side; nulls render as "—", never as a guess. | |
| Served by serve.py @app.get("/cockpit") (registered before the SPA catch-all); | |
| shipped by the wholesale `COPY pages/ ./pages/` in the Dockerfile. | |
| --> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>a11oy — Command Cockpit</title> | |
| <style> | |
| :root{ | |
| --bg:#070b12; --panel:#0c111b; --panel2:#0f1624; --line:#1b2740; | |
| --ink:#e8eefb; --mut:#8a99b8; --dim:#5c6a86; | |
| --ok:#22c55e; --okglow:rgba(34,197,94,.45); | |
| --warn:#f5a524; --down:#ef4444; --downglow:rgba(239,68,68,.4); | |
| --accent:#5e6fff; --accent2:#26d0ce; | |
| } | |
| *{box-sizing:border-box} | |
| html,body{margin:0;height:100%} | |
| body{ | |
| background: | |
| radial-gradient(1100px 620px at 12% -10%, rgba(94,111,255,.14), transparent 60%), | |
| radial-gradient(900px 520px at 100% 0%, rgba(38,208,206,.10), transparent 55%), | |
| var(--bg); | |
| color:var(--ink); | |
| font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; | |
| -webkit-font-smoothing:antialiased; | |
| } | |
| a{color:var(--accent2);text-decoration:none} | |
| a:hover{text-decoration:underline} | |
| .wrap{max-width:1180px;margin:0 auto;padding:28px 22px 60px} | |
| header{display:flex;flex-wrap:wrap;align-items:baseline;gap:14px;margin-bottom:6px} | |
| h1{font-size:22px;margin:0;letter-spacing:.5px} | |
| h1 .glyph{color:var(--accent);text-shadow:0 0 16px rgba(94,111,255,.7)} | |
| .sub{color:var(--mut);font-size:12.5px} | |
| .bar{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin:14px 0 22px; | |
| padding:10px 14px;border:1px solid var(--line);border-radius:12px;background:var(--panel)} | |
| .pill{display:inline-flex;align-items:center;gap:7px;padding:4px 11px;border-radius:999px; | |
| border:1px solid var(--line);background:var(--panel2);font-size:12px;color:var(--mut)} | |
| .dot{width:8px;height:8px;border-radius:50%;background:var(--dim)} | |
| .dot.ok{background:var(--ok);box-shadow:0 0 10px var(--okglow);animation:pulse 2.2s ease-in-out infinite} | |
| .dot.down{background:var(--down);box-shadow:0 0 10px var(--downglow)} | |
| @keyframes pulse{0%,100%{opacity:1}50%{opacity:.45}} | |
| .spacer{flex:1} | |
| .refresh{color:var(--dim);font-size:11.5px} | |
| .grid{display:grid;grid-template-columns:repeat(12,1fr);gap:14px} | |
| .card{grid-column:span 4;border:1px solid var(--line);border-radius:14px; | |
| background:linear-gradient(180deg,var(--panel2),var(--panel));padding:16px 16px 14px; | |
| position:relative;overflow:hidden} | |
| .card.big{grid-column:span 8} | |
| .card.small{grid-column:span 4} | |
| @media(max-width:860px){.card,.card.big,.card.small{grid-column:span 12}} | |
| .card h2{margin:0 0 2px;font-size:12px;letter-spacing:1.4px;text-transform:uppercase;color:var(--mut);font-weight:600} | |
| .metric{font-size:34px;font-weight:700;letter-spacing:.5px;margin:6px 0 2px} | |
| .metric .unit{font-size:15px;color:var(--mut);font-weight:500} | |
| .foot{color:var(--dim);font-size:11.5px} | |
| .chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px} | |
| .chip{display:inline-flex;align-items:center;gap:7px;padding:6px 10px;border-radius:9px; | |
| border:1px solid var(--line);background:#0a0f19;font-size:12px} | |
| .chip .nm{color:var(--ink)} | |
| .chip .st{margin-left:auto;font-size:10.5px;letter-spacing:.6px;color:var(--dim)} | |
| .state-LIVE{color:var(--ok)} .state-DOWN{color:var(--down)} .state-NA{color:var(--dim)} | |
| .kv{display:flex;justify-content:space-between;gap:12px;padding:6px 0;border-bottom:1px dashed var(--line)} | |
| .kv:last-child{border-bottom:0} | |
| .kv .k{color:var(--mut)} .kv .v{color:var(--ink)} | |
| .honest{font-size:10.5px;letter-spacing:.7px;padding:2px 7px;border-radius:6px;border:1px solid var(--line);color:var(--dim)} | |
| .honest.live{color:var(--ok);border-color:rgba(34,197,94,.35)} | |
| .honest.sample{color:var(--warn);border-color:rgba(245,165,36,.3)} | |
| .err{grid-column:span 12;border:1px solid rgba(239,68,68,.4);background:rgba(239,68,68,.06); | |
| border-radius:12px;padding:14px 16px;color:#ffd7d7} | |
| .section-head{display:flex;align-items:baseline;gap:12px;margin:26px 2px 12px; | |
| padding-bottom:8px;border-bottom:1px solid var(--line)} | |
| .section-title{font-size:13px;letter-spacing:1.6px;text-transform:uppercase;color:var(--ink);font-weight:700} | |
| .section-sub{color:var(--mut);font-size:11.5px} | |
| .spine-head{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px; | |
| border:1px solid var(--line);border-radius:14px;padding:14px 16px;background:var(--panel);margin-bottom:12px} | |
| .spine-hero{display:flex;align-items:baseline;gap:10px} | |
| .spine-hero .big{font-size:36px;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums;line-height:1} | |
| .spine-hero .lbl{color:var(--dim);font-size:12px;max-width:280px} | |
| .spine-cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:12px} | |
| .spine-col{border:1px solid var(--line);border-radius:12px;padding:11px 13px;background:var(--panel)} | |
| .spine-col-h{font-size:11px;letter-spacing:1.2px;text-transform:uppercase;color:var(--accent); | |
| font-weight:700;margin-bottom:8px} | |
| .spine-col-h .cnt{color:var(--dim);margin-left:5px;font-weight:600} | |
| .tool{padding:6px 0;border-top:1px solid rgba(255,255,255,.045)} | |
| .tool:first-of-type{border-top:none} | |
| .tool .tn{display:block;font-size:11.5px;color:var(--ink); | |
| font-family:ui-monospace,SFMono-Regular,Menlo,monospace;word-break:break-word} | |
| .tool .td{display:block;font-size:11px;color:var(--dim);line-height:1.4;margin-top:1px} | |
| .con{grid-column:span 3;border:1px solid var(--line);border-radius:12px;background:var(--panel2); | |
| padding:13px 14px;display:flex;flex-direction:column;gap:6px;position:relative} | |
| @media(max-width:860px){.con{grid-column:span 6}} | |
| @media(max-width:560px){.con{grid-column:span 12}} | |
| .con .top{display:flex;align-items:center;gap:8px} | |
| .con .nm{font-weight:700;color:var(--ink);font-size:14px} | |
| .con .kind{color:var(--mut);font-size:11.5px} | |
| .con .blurb{color:var(--dim);font-size:11.5px;line-height:1.45;margin-top:2px} | |
| .con .chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px} | |
| .con .chip{font-size:10px;letter-spacing:.3px;padding:2px 6px;border-radius:5px; | |
| background:rgba(94,111,255,.10);border:1px solid rgba(94,111,255,.22);color:var(--dim); | |
| font-variant-numeric:tabular-nums;white-space:nowrap} | |
| .con .chip.ok{background:rgba(52,199,132,.12);border-color:rgba(52,199,132,.30);color:#8fe6b8} | |
| .con .chip.down{background:rgba(255,90,90,.12);border-color:rgba(255,90,90,.30);color:#ff9a9a} | |
| .con .meta{display:flex;justify-content:space-between;color:var(--dim);font-size:11px;margin-top:2px} | |
| .con a.open{font-size:11.5px} | |
| .group-label{grid-column:1/-1;color:var(--mut);font-size:10.5px;letter-spacing:1.4px; | |
| text-transform:uppercase;margin:8px 2px 0;font-weight:700} | |
| footer{margin-top:26px;color:var(--dim);font-size:11.5px;display:flex;flex-wrap:wrap;gap:16px} | |
| .scan{position:absolute;inset:0;pointer-events:none; | |
| background:linear-gradient(180deg,transparent,rgba(94,111,255,.05) 50%,transparent); | |
| background-size:100% 8px;opacity:.5} | |
| </style> | |
| <link rel="stylesheet" href="/assets/szl-flow.css" data-szl-flow-asset="style" /> | |
| <link rel="stylesheet" href="/assets/szl-holo-v2.css" data-szl-holo-asset="style-v2" /> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <header> | |
| <h1><span class="glyph">⚘</span> a11oy Command Cockpit</h1> | |
| <span class="sub">one screen · the whole organism · live from <code>engine/status</code></span> | |
| </header> | |
| <div class="bar"> | |
| <span class="pill"><span class="dot" id="live-dot"></span><span id="live-label">connecting…</span></span> | |
| <span class="pill">schema <span id="schema" style="color:var(--ink)">—</span></span> | |
| <span class="pill">as of <span id="ts" style="color:var(--ink)">—</span></span> | |
| <span class="spacer"></span> | |
| <span class="refresh">auto-refresh 15s · <a href="/code">full command center →</a></span> | |
| </div> | |
| <div class="grid" id="grid"> | |
| <div class="card small"><h2>loading</h2><div class="metric">…</div></div> | |
| </div> | |
| <div class="section-head"> | |
| <span class="section-title">The Spine</span> | |
| <span class="section-sub">hatun-mcp federates the org’s tools · live from its own manifest · <span id="spine-count">—</span></span> | |
| </div> | |
| <div id="spine"> | |
| <div class="err" style="border-color:var(--line);background:var(--panel);color:var(--mut)">probing the spine…</div> | |
| </div> | |
| <div class="section-head"> | |
| <span class="section-title">Constellation</span> | |
| <span class="section-sub">every SZL flagship surface · live HTTP probe · <span id="con-count">—</span></span> | |
| </div> | |
| <div class="grid" id="constellation"> | |
| <div class="card small"><h2>probing…</h2><div class="metric">…</div></div> | |
| </div> | |
| <footer> | |
| <span>Honest by design — nulls render as “—”, never invented.</span> | |
| <span><a href="/">a11oy home</a></span> | |
| <span><a href="/api/a11oy/v1/engine/status">raw engine/status JSON</a></span> | |
| </footer> | |
| </div> | |
| <script> | |
| (function(){ | |
| var ENDPOINT = "/api/a11oy/v1/engine/status"; | |
| var grid = document.getElementById("grid"); | |
| var liveDot = document.getElementById("live-dot"); | |
| var liveLabel = document.getElementById("live-label"); | |
| function esc(s){return String(s==null?"":s).replace(/[&<>"]/g,function(c){ | |
| return ({"&":"&","<":"<",">":">","\"":"""})[c];});} | |
| function stateOf(reach){ return reach===true?"LIVE":(reach===false?"DOWN":"NA"); } | |
| function fmtTs(iso){ if(!iso) return "—"; try{ var d=new Date(iso); | |
| return d.toISOString().replace("T"," ").replace(/\.\d+Z$/,"Z"); }catch(e){ return esc(iso); } } | |
| function organCard(organs){ | |
| organs = organs || {}; | |
| var names = Object.keys(organs); | |
| var chips = names.map(function(n){ | |
| var o = organs[n]||{}; var st = stateOf(o.reachable); | |
| return '<span class="chip"><span class="dot '+(st==="LIVE"?"ok":(st==="DOWN"?"down":""))+'"></span>'+ | |
| '<span class="nm">'+esc(n)+'</span><span class="st state-'+st+'">'+ | |
| esc((o.status||st)).toUpperCase()+'</span></span>'; | |
| }).join(""); | |
| return chips || '<span class="foot">no organs reported</span>'; | |
| } | |
| function render(d){ | |
| var mind = d.mind||{}, energy = d.energy||{}, swarm = d.swarm||{}; | |
| var healthy = (d.organs_healthy!=null)?d.organs_healthy:"—"; | |
| var total = (d.organs_total!=null)?d.organs_total:"—"; | |
| var allUp = (d.organs_healthy!=null && d.organs_total!=null && d.organs_healthy===d.organs_total); | |
| var eJ = (energy.joules&&energy.joules.value!=null)?energy.joules.value:null; | |
| var eLabel = (energy.joules&&energy.joules.label)?energy.joules.label:"sample"; | |
| // Honesty (Doctrine v11): the backend engine/status endpoint is the single source of | |
| // truth for the MEASURED/SAMPLE label — it only says "measured" when a fresh on-box NVML | |
| // exporter sample exists (szl_joules_truth), and never upgrades to measured otherwise. | |
| // Do NOT infer MEASURED merely because a joules VALUE is present: that produced a | |
| // contradiction where the cockpit showed MEASURED while the header and /energy showed | |
| // SAMPLE for the same metric. Trust the source label. | |
| var eMeasured = (eLabel.toLowerCase()==="measured"); | |
| var eHonest = eMeasured | |
| ? '<span class="honest live">MEASURED</span>' | |
| : '<span class="honest sample">'+esc(eLabel).toUpperCase()+'</span>'; | |
| grid.innerHTML = ''+ | |
| '<div class="card big"><div class="scan"></div><h2>Organism health</h2>'+ | |
| '<div class="metric">'+esc(healthy)+' <span class="unit">/ '+esc(total)+' organs '+(allUp?'nominal':'')+'</span></div>'+ | |
| '<div class="foot">brain · heart · blood · immune · skeleton · nervous</div>'+ | |
| '<div class="chips">'+organCard(d.organs)+'</div>'+ | |
| '</div>'+ | |
| '<div class="card small"><h2>Mind</h2>'+ | |
| '<div class="metric" style="font-size:22px">'+esc((mind.posture||"—")).toUpperCase()+'</div>'+ | |
| '<div class="kv"><span class="k">sovereign</span><span class="v">'+esc(mind.sovereign===true?"yes":(mind.sovereign===false?"no":"—"))+'</span></div>'+ | |
| '<div class="kv"><span class="k">inference</span><span class="v state-'+stateOf(mind.inference&&mind.inference.reachable)+'">'+stateOf(mind.inference&&mind.inference.reachable)+'</span></div>'+ | |
| '<div class="kv"><span class="k">gpu</span><span class="v">'+esc(mind.gpu!=null?mind.gpu:"—")+'</span></div>'+ | |
| '</div>'+ | |
| '<div class="card small"><h2>Energy '+eHonest+'</h2>'+ | |
| '<div class="metric" style="font-size:24px">'+(eJ!=null?esc(eJ)+' <span class="unit">J'+(eMeasured?'':' ('+esc(eLabel)+')')+'</span>':'<span class="unit">no measured sample</span>')+'</div>'+ | |
| '<div class="kv"><span class="k">within bound</span><span class="v">'+esc(energy.within_bound==null?"—":energy.within_bound)+'</span></div>'+ | |
| '<div class="kv"><span class="k">source</span><span class="v">'+esc(energy.source!=null?energy.source:"—")+'</span></div>'+ | |
| '</div>'+ | |
| '<div class="card small"><h2>Swarm</h2>'+ | |
| (swarm.status==="standalone" | |
| ? '<div class="metric state-NA" style="font-size:22px">STANDALONE</div>'+ | |
| '<div class="foot">'+esc(swarm.detail||"no swarm mesh on this Space (single-node)")+'</div>' | |
| : '<div class="metric state-'+stateOf(swarm.reachable)+'" style="font-size:22px">'+stateOf(swarm.reachable)+'</div>'+ | |
| '<div class="foot">'+esc(swarm.reachable===false?"no swarm peers reachable":"mesh peers")+'</div>')+ | |
| '</div>'+ | |
| '<div class="card small"><h2>Sections</h2>'+ | |
| '<div class="kv"><span class="k">Formulas</span><a class="v" href="/formulas">open →</a></div>'+ | |
| '<div class="kv"><span class="k">Signed Receipts</span><a class="v" href="/code">open →</a></div>'+ | |
| '<div class="kv"><span class="k">Governance / Eval</span><a class="v" href="/governance">open →</a></div>'+ | |
| '<div class="kv"><span class="k">Energy / Compute</span><a class="v" href="/energy-ops">open →</a></div>'+ | |
| '</div>'; | |
| document.getElementById("schema").textContent = d.schema||"—"; | |
| document.getElementById("ts").textContent = fmtTs(d.ts_utc); | |
| liveDot.className = "dot ok"; liveLabel.textContent = "LIVE"; | |
| } | |
| function fail(msg){ | |
| liveDot.className = "dot down"; liveLabel.textContent = "UNREACHABLE"; | |
| grid.innerHTML = '<div class="err">Engine status is unreachable ('+esc(msg)+ | |
| '). No data is shown rather than fabricated — retrying every 15s.</div>'; | |
| } | |
| function tick(){ | |
| fetch(ENDPOINT, {headers:{"accept":"application/json"}, cache:"no-store"}) | |
| .then(function(r){ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) | |
| .then(render) | |
| .catch(function(e){ fail(e.message||String(e)); }); | |
| } | |
| var conEl = document.getElementById("constellation"); | |
| var conCount = document.getElementById("con-count"); | |
| function renderCon(d){ | |
| var s = (d && d.surfaces) || []; | |
| conCount.textContent = (d.reachable!=null?d.reachable:"—")+" / "+(d.total!=null?d.total:"—")+" reachable"; | |
| function reportChips(r){ | |
| if(!r) return ''; | |
| var chips = []; | |
| if(r.service) chips.push('<span class="chip">'+esc(r.service)+'</span>'); | |
| if(r.version) chips.push('<span class="chip">v'+esc(String(r.version).replace(/^v/,''))+'</span>'); | |
| if(r.doctrine) chips.push('<span class="chip">doctrine '+esc(r.doctrine)+'</span>'); | |
| if(r.commit) chips.push('<span class="chip">@'+esc(String(r.commit).slice(0,8))+'</span>'); | |
| if(r.protocol) chips.push('<span class="chip">mcp '+esc(r.protocol)+'</span>'); | |
| if(r.chain_verified===true) chips.push('<span class="chip ok">chain \u2713'+(r.signer?' '+esc(r.signer):'')+'</span>'); | |
| if(r.chain_verified===false) chips.push('<span class="chip down">chain \u2717</span>'); | |
| if(r.honesty_tier) chips.push('<span class="chip">'+esc(r.honesty_tier)+'</span>'); | |
| if(!chips.length) return ''; | |
| return '<div class="chips" title="Self-reported by the surface\u2019s own /healthz">'+chips.join('')+'</div>'; | |
| } | |
| function tile(x){ | |
| var live = x.reachable===true; | |
| return '<div class="con"><div class="top">'+ | |
| '<span class="dot '+(live?"ok":"down")+'"></span>'+ | |
| '<span class="nm">'+esc(x.name)+'</span>'+ | |
| '<span class="st state-'+(live?"LIVE":"DOWN")+'" style="margin-left:auto;font-size:10.5px;letter-spacing:.6px">'+(live?"LIVE":"DOWN")+'</span></div>'+ | |
| '<div class="kind">'+esc(x.kind)+'</div>'+ | |
| (x.blurb ? '<div class="blurb">'+esc(x.blurb)+'</div>' : '')+ | |
| (live ? reportChips(x.report) : '')+ | |
| '<div class="meta"><span>HTTP '+esc(x.http_status)+'</span><span>'+esc(x.latency_ms)+' ms</span></div>'+ | |
| '<a class="open" href="'+esc(x.url)+'" target="_blank" rel="noopener">open →</a>'+ | |
| '</div>'; | |
| } | |
| if(!s.length){ conEl.innerHTML = '<div class="err">No surfaces reported.</div>'; return; } | |
| var groups = [], seen = {}; | |
| s.forEach(function(x){ var g=x.group||"Other"; if(!seen[g]){seen[g]=[];groups.push(g);} seen[g].push(x); }); | |
| conEl.innerHTML = groups.map(function(g){ | |
| return '<div class="group-label">'+esc(g)+'</div>'+ seen[g].map(tile).join(""); | |
| }).join(""); | |
| } | |
| function conFail(msg){ | |
| conCount.textContent = "probe unreachable"; | |
| conEl.innerHTML = '<div class="err">Constellation probe unreachable ('+esc(msg)+'). Retrying.</div>'; | |
| } | |
| function tickCon(){ | |
| fetch("/api/a11oy/v1/constellation", {headers:{"accept":"application/json"}, cache:"no-store"}) | |
| .then(function(r){ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) | |
| .then(renderCon) | |
| .catch(function(e){ conFail(e.message||String(e)); }); | |
| } | |
| var spineEl = document.getElementById("spine"); | |
| var spineCount = document.getElementById("spine-count"); | |
| var SPINE_LABELS = {a11oy:"a11oy", killinchu:"killinchu", immune:"immune", governance:"governance / doctrine"}; | |
| var SPINE_ORDER = ["a11oy","killinchu","immune","governance"]; | |
| function renderSpine(d){ | |
| if(!d || d.reachable===false){ | |
| spineCount.textContent = "spine unreachable"; | |
| spineEl.innerHTML = '<div class="err">hatun-mcp manifest unreachable (HTTP '+esc(d&&d.http_status)+'). Retrying.</div>'; | |
| return; | |
| } | |
| var gov = d.governance||{}, locked = gov.doctrine_locked||{}, srv = d.server||{}; | |
| spineCount.textContent = (d.tool_count!=null?d.tool_count:"—")+" federated tools"; | |
| var chips = []; | |
| if(srv.version) chips.push('<span class="chip">hatun v'+esc(srv.version)+'</span>'); | |
| if(gov.protocol_revision) chips.push('<span class="chip">mcp '+esc(gov.protocol_revision)+'</span>'); | |
| if(gov.signer_mode) chips.push('<span class="chip ok">signer '+esc(gov.signer_mode)+'</span>'); | |
| if(locked.lean_declarations!=null) chips.push('<span class="chip">'+esc(locked.lean_declarations)+' lean decls</span>'); | |
| if(locked.yuyay_axes!=null) chips.push('<span class="chip">Yuyay-'+esc(locked.yuyay_axes)+'</span>'); | |
| if(d.auth_required) chips.push('<span class="chip">calls need an SZL key</span>'); | |
| var byp = {}; (d.tools||[]).forEach(function(t){ (byp[t.product]=byp[t.product]||[]).push(t); }); | |
| var seen = SPINE_ORDER.filter(function(p){return byp[p];}); | |
| Object.keys(byp).forEach(function(p){ if(seen.indexOf(p)<0) seen.push(p); }); | |
| var cols = seen.map(function(p){ | |
| var list = byp[p].map(function(t){ | |
| return '<div class="tool"><span class="tn">'+esc(t.name)+'</span>'+ | |
| (t.description?'<span class="td">'+esc(t.description)+'</span>':'')+'</div>'; | |
| }).join(""); | |
| return '<div class="spine-col"><div class="spine-col-h">'+esc(SPINE_LABELS[p]||p)+ | |
| '<span class="cnt">'+byp[p].length+'</span></div>'+list+'</div>'; | |
| }).join(""); | |
| spineEl.innerHTML = | |
| '<div class="spine-head">'+ | |
| '<div class="spine-hero"><span class="big">'+esc(d.tool_count)+'</span>'+ | |
| '<span class="lbl">tools federated through one signed gateway — read live from hatun-mcp’s own manifest</span></div>'+ | |
| (chips.length?'<div class="chips">'+chips.join("")+'</div>':'')+ | |
| '</div>'+ | |
| '<div class="spine-cols">'+cols+'</div>'; | |
| } | |
| function tickSpine(){ | |
| fetch("/api/a11oy/v1/spine", {headers:{"accept":"application/json"}, cache:"no-store"}) | |
| .then(function(r){ if(!r.ok) throw new Error("HTTP "+r.status); return r.json(); }) | |
| .then(renderSpine) | |
| .catch(function(e){ spineCount.textContent="spine unreachable"; | |
| spineEl.innerHTML='<div class="err">Spine probe unreachable ('+esc(e.message||String(e))+'). Retrying.</div>'; }); | |
| } | |
| tick(); setInterval(tick, 15000); | |
| tickSpine(); setInterval(tickSpine, 60000); | |
| tickCon(); setInterval(tickCon, 30000); | |
| })(); | |
| </script> | |
| <script src="/assets/szl-flow.js" defer data-szl-flow-asset="script"></script> | |
| <script src="/assets/szl-holo-v2.js" defer data-szl-holo-asset="script-v2"></script> | |
| </body> | |
| </html> | |