<!DOCTYPE html>
<html lang="en" data-theme="">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'" />
<title>{{PAGE_TITLE}}</title>
<style>
:root[data-theme="dark"] {
--color-background-primary: #1a1a1a;
--color-background-secondary: #242424;
--color-background-tertiary: #2e2e2e;
--color-background-info: #18324d;
--color-background-danger: #3d1f1f;
--color-background-warning: #3d2f1f;
--color-background-success: #1f3d2f;
--color-text-primary: #e8e8e8;
--color-text-secondary: #a0a0a0;
--color-text-tertiary: #707070;
--color-text-info: #6babff;
--color-text-danger: #ff7a7a;
--color-text-warning: #f0b05f;
--color-text-success: #5fd495;
--color-border-tertiary: rgba(255,255,255,0.1);
--color-border-secondary: rgba(255,255,255,0.2);
--color-border-info: #378add;
--color-border-success: #3d7d5c;
--page-bg: #121212;
}
:root[data-theme="light"] {
--color-background-primary: #ffffff;
--color-background-secondary: #f5f5f5;
--color-background-tertiary: #ececec;
--color-background-info: #e6f1fb;
--color-background-danger: #fcebeb;
--color-background-warning: #faeeda;
--color-background-success: #eaf3de;
--color-text-primary: #1a1a1a;
--color-text-secondary: #555555;
--color-text-tertiary: #888888;
--color-text-info: #0c447c;
--color-text-danger: #791f1f;
--color-text-warning: #633806;
--color-text-success: #27500a;
--color-border-tertiary: rgba(0,0,0,0.1);
--color-border-secondary: rgba(0,0,0,0.2);
--color-border-info: #378add;
--color-border-success: #7ab85a;
--page-bg: #fafafa;
}
:root {
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', 'SF Mono', Consolas, Monaco, monospace;
--border-radius-md: 8px;
--border-radius-lg: 12px;
}
* { box-sizing: border-box; }
body { margin: 0; padding: 24px; background: var(--page-bg); color: var(--color-text-primary); font-family: var(--font-sans); font-size: 13px; line-height: 1.5; }
.ps-container { max-width: 1100px; margin: 0 auto; }
.ps-header-ctrl { display: flex; justify-content: flex-end; align-items: center; margin-bottom: 10px; font-size: 11px; color: var(--color-text-tertiary); }
.ps-theme-toggle { background: transparent; border: 0.5px solid var(--color-border-tertiary); color: var(--color-text-secondary); padding: 4px 10px; border-radius: var(--border-radius-md); cursor: pointer; font-size: 11px; font-family: inherit; }
.ps-theme-toggle:hover { border-color: var(--color-border-secondary); }
.ps-topbar { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border: 0.5px solid var(--color-border-tertiary); border-radius: var(--border-radius-md); background: var(--color-background-secondary); margin-bottom: 12px; flex-wrap: wrap; }
.ps-brand { font-weight: 500; font-size: 14px; }
.ps-meta { color: var(--color-text-secondary); font-size: 12px; font-family: var(--font-mono); }
.ps-gate-pass { margin-left: auto; background: var(--color-background-success); color: var(--color-text-success); padding: 3px 10px; border-radius: var(--border-radius-md); font-size: 11px; font-weight: 500; }
.ps-gate-fail { margin-left: auto; background: var(--color-background-danger); color: var(--color-text-danger); padding: 3px 10px; border-radius: var(--border-radius-md); font-size: 11px; font-weight: 500; }
.ps-tabs { display: flex; gap: 2px; border-bottom: 0.5px solid var(--color-border-tertiary); margin-bottom: 14px; }
.ps-tab { padding: 8px 14px; font-size: 13px; cursor: pointer; border: none; background: transparent; color: var(--color-text-secondary); border-bottom: 2px solid transparent; margin-bottom: -0.5px; font-family: inherit; }
.ps-tab:hover { color: var(--color-text-primary); }
.ps-tab.active { color: var(--color-text-primary); border-bottom-color: var(--color-text-primary); font-weight: 500; }
.ps-tab .ps-badge { display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 10px; font-size: 10px; background: var(--color-background-tertiary); color: var(--color-text-secondary); }
.ps-metrics { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 14px; }
.ps-metrics-3 { grid-template-columns: repeat(3, 1fr); }
.ps-metric { background: var(--color-background-secondary); border-radius: var(--border-radius-md); padding: 10px 12px; }
.ps-metric-label { font-size: 11px; color: var(--color-text-secondary); margin-bottom: 4px; }
.ps-metric-value { font-size: 20px; font-weight: 500; font-family: var(--font-mono); word-break: break-word; }
.ps-metric-value-sm { font-size: 14px; line-height: 1.4; }
.ps-metric-sub { font-size: 11px; color: var(--color-text-tertiary); font-family: var(--font-mono); }
.ps-filters { display: flex; gap: 6px; margin-bottom: 10px; flex-wrap: wrap; align-items: center; }
.ps-chip-group { display: contents; }
.ps-chip { padding: 4px 10px; font-size: 11px; border-radius: 12px; border: 0.5px solid var(--color-border-tertiary); background: transparent; cursor: pointer; color: var(--color-text-secondary); font-family: inherit; }
.ps-chip:hover { background: var(--color-background-secondary); }
.ps-chip.active { background: var(--color-text-primary); color: var(--color-background-primary); border-color: var(--color-text-primary); }
.ps-list { display: flex; flex-direction: column; gap: 6px; }
.ps-row { display: grid; grid-template-columns: 60px 1fr auto; align-items: center; gap: 10px; padding: 10px 12px; border: 0.5px solid var(--color-border-tertiary); border-radius: var(--border-radius-md); cursor: pointer; background: var(--color-background-primary); }
.ps-row:hover { background: var(--color-background-secondary); border-color: var(--color-border-secondary); }
.ps-row.selected { border: 2px solid var(--color-border-info); padding: 9px 11px; }
.ps-sev-crit { background: var(--color-background-danger); color: var(--color-text-danger); font-size: 10px; font-weight: 500; padding: 3px 8px; border-radius: var(--border-radius-md); text-align: center; }
.ps-sev-warn { background: var(--color-background-warning); color: var(--color-text-warning); font-size: 10px; font-weight: 500; padding: 3px 8px; border-radius: var(--border-radius-md); text-align: center; }
.ps-sev-info { background: var(--color-background-info); color: var(--color-text-info); font-size: 10px; font-weight: 500; padding: 3px 8px; border-radius: var(--border-radius-md); text-align: center; }
.ps-fin-main { min-width: 0; }
.ps-fin-type { font-weight: 500; font-size: 13px; margin-bottom: 2px; }
.ps-fin-service { color: var(--color-text-tertiary); font-weight: 400; font-size: 11px; }
.ps-fin-detail { font-size: 11px; color: var(--color-text-secondary); font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ps-fin-right { font-size: 11px; color: var(--color-text-secondary); font-family: var(--font-mono); text-align: right; white-space: nowrap; }
.ps-fin-right-sub { color: var(--color-text-tertiary); }
.ps-tree-header { font-size: 11px; color: var(--color-text-secondary); margin-bottom: 8px; font-family: var(--font-mono); word-break: break-all; }
.ps-tree { font-family: var(--font-mono); font-size: 12px; padding: 10px 12px; background: var(--color-background-secondary); border-radius: var(--border-radius-md); line-height: 1.9; }
.ps-span { display: grid; grid-template-columns: 1fr auto; gap: 10px; padding: 2px 4px; border-radius: 4px; }
.ps-span.dim { color: var(--color-text-tertiary); }
.ps-span.hilite { background: var(--color-background-danger); }
.ps-span-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ps-span-dur { color: var(--color-text-tertiary); white-space: nowrap; }
.ps-span-find { color: var(--color-text-danger); font-weight: 500; }
.ps-drill { background: var(--color-background-info); border: 0.5px solid var(--color-border-info); border-radius: var(--border-radius-md); padding: 10px 12px; margin-top: 10px; font-size: 12px; color: var(--color-text-info); }
.ps-drill-label { font-size: 10px; color: var(--color-text-info); margin-bottom: 4px; opacity: 0.8; }
.ps-drill-link { color: var(--color-text-info); }
.ps-banner { background: var(--color-background-info); border: 0.5px solid var(--color-border-info); border-radius: var(--border-radius-md); padding: 8px 12px; margin-bottom: 10px; font-size: 11px; color: var(--color-text-info); }
.ps-empty { padding: 30px; text-align: center; color: var(--color-text-secondary); font-size: 13px; background: var(--color-background-secondary); border-radius: var(--border-radius-md); }
.ps-table { width: 100%; font-size: 11px; font-family: var(--font-mono); border-collapse: collapse; }
.ps-table th { text-align: left; font-weight: 500; padding: 6px 8px; border-bottom: 0.5px solid var(--color-border-tertiary); color: var(--color-text-secondary); font-size: 10px; font-family: var(--font-sans); }
.ps-table td { padding: 6px 8px; border-bottom: 0.5px solid var(--color-border-tertiary); vertical-align: top; }
#pgstat-body tr.hilite { background: var(--color-background-danger); }
#pgstat-body tr.hilite td:first-child { color: var(--color-text-danger); font-weight: 500; }
.ps-footer { margin-top: 14px; font-size: 11px; color: var(--color-text-tertiary); text-align: center; }
.ps-credit { margin-top: 8px; font-size: 11px; color: var(--color-text-tertiary); text-align: center; }
.ps-credit a { color: var(--color-text-secondary); text-decoration: underline; }
.ps-credit a:hover { color: var(--color-text-primary); }
.ps-kbd { font-family: var(--font-mono); font-size: 10px; background: var(--color-background-tertiary); padding: 1px 5px; border-radius: 3px; color: var(--color-text-secondary); }
.ps-search { display: none; width: 100%; padding: 6px 10px; margin-bottom: 10px; font-size: 12px; font-family: var(--font-sans); background: var(--color-background-secondary); border: 0.5px solid var(--color-border-tertiary); border-radius: var(--border-radius-md); color: var(--color-text-primary); }
.ps-search:focus { outline: none; border-color: var(--color-border-info); }
.ps-span.ps-span-pgstat-link { cursor: pointer; }
.ps-span.ps-span-pgstat-link:hover .ps-span-text { text-decoration: underline; }
.ps-drill-clear { color: var(--color-text-info); text-decoration: underline; cursor: pointer; margin-left: 8px; }
.ps-diff-section { margin-bottom: 18px; }
.ps-diff-section-header { font-size: 12px; font-weight: 500; margin-bottom: 8px; color: var(--color-text-primary); }
.ps-diff-section-header.red { color: var(--color-text-danger); }
.ps-diff-section-header.green { color: var(--color-text-success); }
.ps-panel-toolbar { display: flex; justify-content: flex-end; margin-bottom: 8px; }
.ps-export-btn { padding: 4px 10px; font-size: 11px; border-radius: var(--border-radius-md); border: 0.5px solid var(--color-border-info); background: transparent; cursor: pointer; color: var(--color-text-info); font-family: inherit; }
.ps-export-btn:hover { background: var(--color-background-info); }
.ps-copy-link-btn { padding: 4px 10px; font-size: 11px; border-radius: var(--border-radius-md); border: 0.5px solid var(--color-border-info); background: transparent; cursor: pointer; color: var(--color-text-info); font-family: inherit; margin-left: 6px; }
.ps-copy-link-btn:hover { background: var(--color-background-info); }
.ps-correlation-clickable { cursor: pointer; }
.ps-correlation-clickable:hover { background: var(--color-background-tertiary); }
.ps-show-more { margin-top: 10px; padding: 6px 12px; font-size: 11px; border-radius: var(--border-radius-md); border: 0.5px solid var(--color-border-tertiary); background: transparent; color: var(--color-text-secondary); cursor: pointer; font-family: inherit; width: 100%; }
.ps-show-more:hover { background: var(--color-background-secondary); border-color: var(--color-border-secondary); }
dialog.ps-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: 520px; width: calc(100% - 40px); background: var(--color-background-secondary); color: var(--color-text-primary); border-radius: var(--border-radius-lg); border: 0.5px solid var(--color-border-tertiary); padding: 18px 22px; box-shadow: 0 8px 32px rgba(0,0,0,0.4); }
dialog.ps-modal::backdrop { background: rgba(0,0,0,0.6); }
dialog.ps-modal h2 { margin: 0 0 12px 0; font-size: 14px; font-weight: 500; }
.ps-modal-close { position: absolute; top: 8px; right: 10px; background: transparent; border: none; color: var(--color-text-secondary); font-family: inherit; font-size: 16px; cursor: pointer; line-height: 1; padding: 4px 8px; }
.ps-modal-close:hover { color: var(--color-text-primary); }
dialog.ps-modal table { width: 100%; border-collapse: collapse; }
dialog.ps-modal th { text-align: left; padding: 4px 6px; font-size: 10px; font-weight: 500; color: var(--color-text-secondary); border-bottom: 0.5px solid var(--color-border-tertiary); font-family: var(--font-sans); }
dialog.ps-modal th:first-child { width: 90px; }
dialog.ps-modal td { padding: 5px 6px; font-size: 12px; vertical-align: top; }
dialog.ps-modal td:first-child { width: 90px; white-space: nowrap; }
</style>
</head>
<body>
<div class="ps-container">
<div class="ps-header-ctrl">
<button class="ps-theme-toggle" id="theme-toggle" type="button">Toggle theme</button>
</div>
<div class="ps-topbar" id="topbar"></div>
<div class="ps-tabs" id="tabs" role="tablist" aria-label="Dashboard sections"></div>
<div id="panel-findings" role="tabpanel" aria-labelledby="tab-findings">
<div class="ps-banner" id="trim-banner" style="display: none;"></div>
<input type="search" class="ps-search" id="findings-search" placeholder="Filter findings by type, service, endpoint or template..." aria-label="Filter findings" />
<div class="ps-metrics" id="findings-metrics"></div>
<div class="ps-filters" id="findings-filters"></div>
<div class="ps-panel-toolbar">
<button type="button" class="ps-export-btn" id="findings-export" data-export-tab="findings">Export CSV</button>
<button type="button" class="ps-copy-link-btn" id="findings-copy-link" data-copy-link-tab="findings">Copy link</button>
</div>
<div class="ps-list" id="findings-list"></div>
<button type="button" class="ps-show-more" id="findings-show-more" style="display: none;"></button>
<div class="ps-empty" id="findings-empty" style="display: none;">No findings in this trace set.</div>
</div>
<div id="panel-explain" role="tabpanel" aria-labelledby="tab-explain" style="display: none;">
<div class="ps-empty" id="explain-empty">Click a finding to view its trace tree here.</div>
<div id="explain-content" style="display: none;">
<div class="ps-tree-header" id="explain-breadcrumb"></div>
<div class="ps-tree" id="explain-tree"></div>
<div class="ps-drill" id="explain-fix" style="display: none;"></div>
</div>
<div class="ps-empty" id="explain-not-embedded" style="display: none;"></div>
</div>
<div id="panel-pgstat" role="tabpanel" aria-labelledby="tab-pgstat" style="display: none;">
<div class="ps-drill" id="pgstat-drill" style="display: none;">
<div class="ps-drill-label">Filtered from Explain</div>
<span id="pgstat-drill-text"></span>
<span class="ps-drill-clear" id="pgstat-drill-clear">clear</span>
</div>
<div class="ps-filters" id="pgstat-rankings"></div>
<input type="search" class="ps-search" id="pgstat-search" placeholder="Filter by SQL template..." aria-label="Filter pg_stat rows" />
<div class="ps-panel-toolbar">
<button type="button" class="ps-export-btn" id="pgstat-export" data-export-tab="pgstat">Export CSV</button>
<button type="button" class="ps-copy-link-btn" id="pgstat-copy-link" data-copy-link-tab="pgstat">Copy link</button>
</div>
<table class="ps-table" id="pgstat-table">
<thead><tr><th>Template</th><th style="width: 80px;">Calls</th><th style="width: 100px;">Total ms</th><th style="width: 90px;">Mean ms</th></tr></thead>
<tbody id="pgstat-body"></tbody>
</table>
<div class="ps-empty" id="pgstat-empty" style="display: none;">No pg_stat entries.</div>
</div>
<div id="panel-diff" role="tabpanel" aria-labelledby="tab-diff" style="display: none;">
<input type="search" class="ps-search" id="diff-search" placeholder="Filter diff findings by type, service, endpoint or template..." aria-label="Filter diff findings" />
<div class="ps-panel-toolbar">
<button type="button" class="ps-export-btn" id="diff-export" data-export-tab="diff">Export CSV</button>
<button type="button" class="ps-copy-link-btn" id="diff-copy-link" data-copy-link-tab="diff">Copy link</button>
</div>
<div class="ps-diff-section">
<div class="ps-diff-section-header red" id="diff-new-header">New findings (0)</div>
<div class="ps-list" id="diff-new-list"></div>
</div>
<div class="ps-diff-section">
<div class="ps-diff-section-header green" id="diff-resolved-header">Resolved findings (0)</div>
<div class="ps-list" id="diff-resolved-list"></div>
</div>
<div class="ps-diff-section">
<div class="ps-diff-section-header" id="diff-sev-header">Severity changes (0)</div>
<table class="ps-table" id="diff-sev-table" style="display: none;">
<thead><tr><th>Type</th><th>Service</th><th>Endpoint</th><th>Before</th><th>After</th></tr></thead>
<tbody id="diff-sev-body"></tbody>
</table>
</div>
<div class="ps-diff-section">
<div class="ps-diff-section-header" id="diff-endp-header">Endpoint metric deltas (0)</div>
<table class="ps-table" id="diff-endp-table" style="display: none;">
<thead><tr><th>Service</th><th>Endpoint</th><th>Before I/O</th><th>After I/O</th><th>Delta</th></tr></thead>
<tbody id="diff-endp-body"></tbody>
</table>
</div>
</div>
<div id="panel-correlations" role="tabpanel" aria-labelledby="tab-correlations" style="display: none;">
<input type="search" class="ps-search" id="correlations-search" placeholder="Filter correlations by service or type..." aria-label="Filter correlations" />
<div class="ps-panel-toolbar">
<button type="button" class="ps-export-btn" id="correlations-export" data-export-tab="correlations">Export CSV</button>
<button type="button" class="ps-copy-link-btn" id="correlations-copy-link" data-copy-link-tab="correlations">Copy link</button>
</div>
<div class="ps-list" id="correlations-list"></div>
</div>
<div id="panel-green" role="tabpanel" aria-labelledby="tab-green" style="display: none;">
<div class="ps-metrics ps-metrics-3" id="green-metrics"></div>
<div class="ps-tree-header" id="green-regions-header" style="margin-top: 14px;"></div>
<table class="ps-table" id="green-regions-table" style="display: none;">
<thead><tr><th>Region</th><th>Intensity (gCO2/kWh)</th><th>I/O ops</th><th>CO2</th><th>Source</th></tr></thead>
<tbody id="green-regions-body"></tbody>
</table>
<div class="ps-empty" id="green-regions-empty" style="display: none;">No region breakdown available.</div>
</div>
<div class="ps-footer">
<span class="ps-kbd">j</span>/<span class="ps-kbd">k</span> navigate . <span class="ps-kbd">enter</span> open . <span class="ps-kbd">/</span> search . <span class="ps-kbd">esc</span> back . <span class="ps-kbd">?</span> shortcuts
</div>
<div class="ps-credit">
Powered by <a href="https://github.com/robintra/perf-sentinel" target="_blank" rel="noopener noreferrer">perf-sentinel</a>
</div>
</div>
<dialog id="cheatsheet" class="ps-modal" aria-labelledby="cheatsheet-title">
<button type="button" class="ps-modal-close" id="cheatsheet-close" aria-label="Close">x</button>
<h2 id="cheatsheet-title">Keyboard shortcuts</h2>
<table id="cheatsheet-table">
<thead><tr><th scope="col">Key</th><th scope="col">Action</th></tr></thead>
<tbody id="cheatsheet-body"></tbody>
</table>
</dialog>
<script id="report-data" type="application/json">
{{REPORT_JSON}}
</script>
<script>
(function () {
"use strict";
var STORAGE_KEYS = {
theme: "perf-sentinel:theme",
pgstatRanking: "perf-sentinel:pgstat-ranking"
};
function sessionGet(key) {
try {
return globalThis.sessionStorage.getItem(key);
} catch {
return null;
}
}
function sessionSet(key, value) {
try {
globalThis.sessionStorage.setItem(key, value);
} catch {
return;
}
}
var THEME_MODES = ["auto", "dark", "light"];
var prefersDarkMQ = null;
try {
prefersDarkMQ = globalThis.matchMedia
? globalThis.matchMedia("(prefers-color-scheme: dark)")
: null;
} catch {
prefersDarkMQ = null;
}
function currentThemeMode() {
var stored = sessionGet(STORAGE_KEYS.theme);
return THEME_MODES.includes(stored) ? stored : "auto";
}
function resolveThemeColor(mode) {
if (mode === "dark" || mode === "light") return mode;
return prefersDarkMQ && prefersDarkMQ.matches ? "dark" : "light";
}
function applyTheme(mode) {
document.documentElement.dataset.theme = resolveThemeColor(mode);
var btn = document.getElementById("theme-toggle");
if (btn) btn.textContent = "Theme: " + mode;
}
applyTheme(currentThemeMode());
if (prefersDarkMQ && typeof prefersDarkMQ.addEventListener === "function") {
try {
prefersDarkMQ.addEventListener("change", function () {
if (currentThemeMode() === "auto") applyTheme("auto");
});
} catch {
}
}
function loadPayload() {
var raw = document.getElementById("report-data").textContent || "";
try {
return JSON.parse(raw);
} catch (e) {
document.body.textContent = "Failed to parse embedded report data: " + e.message;
return null;
}
}
function indexTracesById(list) {
var map = Object.create(null);
list.forEach(function (trace) { map[trace.trace_id] = trace; });
return map;
}
var payload = loadPayload();
if (!payload) return;
var report = payload.report || {};
var findings = report.findings || [];
var greenSummary = report.green_summary || null;
var hasGreen = !!(greenSummary && greenSummary.co2);
var embeddedTraces = payload.embedded_traces || [];
var tracesById = indexTracesById(embeddedTraces);
function el(tag, cls, text) {
var n = document.createElement(tag);
if (cls) n.className = cls;
if (text !== undefined && text !== null) n.textContent = String(text);
return n;
}
function setAttr(node, name, value) { node.setAttribute(name, String(value)); }
function safeHttpsHref(url) {
if (typeof url !== "string") return null;
if (!url.startsWith("https://")) return null;
return url;
}
function metricCard(label, value, sub, smallValue) {
var wrap = el("div", "ps-metric");
wrap.appendChild(el("div", "ps-metric-label", label));
var v = el("div", "ps-metric-value", value);
if (smallValue) v.classList.add("ps-metric-value-sm");
wrap.appendChild(v);
wrap.appendChild(el("div", "ps-metric-sub", sub));
return wrap;
}
function formatGco2(grams) {
if (!Number.isFinite(grams)) return "-";
var abs = Math.abs(grams);
if (abs >= 1) return grams.toFixed(3) + " g";
if (abs >= 0.001) return (grams * 1000).toFixed(3) + " mg";
if (abs >= 0.000001) return (grams * 1000000).toFixed(3) + " ug";
if (abs === 0) return "0 g";
return grams.toExponential(2) + " g";
}
function formatDurationUs(us) {
if (!Number.isFinite(us)) return "-";
var ms = us / 1000;
if (ms < 1) return ms.toFixed(2) + " ms";
if (ms < 10) return ms.toFixed(2) + " ms";
return Math.round(ms) + " ms";
}
function truncate(s, max) {
if (typeof s !== "string") return "";
if (s.length <= max) return s;
return s.slice(0, max - 1) + "...";
}
function percent(num, denom) {
if (!denom) return "0%";
return Math.round((num * 100) / denom) + "%";
}
var SEV_CLASS = { critical: "ps-sev-crit", warning: "ps-sev-warn", info: "ps-sev-info" };
var SEV_LABEL = { critical: "CRIT", warning: "WARN", info: "INFO" };
var TYPE_LABEL = {
n_plus_one_sql: "N+1 SQL",
n_plus_one_http: "N+1 HTTP",
redundant_sql: "Redundant SQL",
redundant_http: "Redundant HTTP",
slow_sql: "Slow SQL",
slow_http: "Slow HTTP",
excessive_fanout: "Excessive fanout",
chatty_service: "Chatty service",
pool_saturation: "Pool saturation",
serialized_calls: "Serialized calls"
};
function typeLabel(t) { return TYPE_LABEL[t] || String(t); }
function renderTopbar() {
var bar = document.getElementById("topbar");
var brand = el("span", "ps-brand", "perf-sentinel");
var version = el("span", "ps-meta", "v" + (payload.version || ""));
var analysis = report.analysis || { events_processed: 0, traces_analyzed: 0 };
var meta = el(
"span",
"ps-meta",
(payload.input_label || "-") +
" . " + analysis.traces_analyzed + " traces" +
" . " + analysis.events_processed + " spans"
);
var gate = report.quality_gate || { passed: true };
var badge = el(
"span",
gate.passed ? "ps-gate-pass" : "ps-gate-fail",
gate.passed ? "Quality gate passed" : "Quality gate failed"
);
bar.appendChild(brand);
bar.appendChild(version);
bar.appendChild(meta);
bar.appendChild(badge);
}
var tabs = [];
var registeredTabs = Object.create(null);
function registerTab(key, label, countFn) {
tabs.push({ key: key, label: label, countFn: countFn });
registeredTabs[key] = true;
}
function tabIsRegistered(key) { return !!registeredTabs[key]; }
function renderTabs() {
var bar = document.getElementById("tabs");
tabs.forEach(function (t) {
var btn = el("button", "ps-tab", t.label);
btn.type = "button";
setAttr(btn, "role", "tab");
setAttr(btn, "data-tab", t.key);
setAttr(btn, "id", "tab-" + t.key);
setAttr(btn, "aria-controls", "panel-" + t.key);
setAttr(btn, "aria-selected", "false");
setAttr(btn, "tabindex", "-1");
if (t.countFn) {
var c = t.countFn();
if (c !== null && c !== undefined) {
var badge = el("span", "ps-badge", String(c));
btn.appendChild(document.createTextNode(" "));
btn.appendChild(badge);
}
}
btn.addEventListener("click", function () { switchTab(t.key); });
bar.appendChild(btn);
});
wireTablistKeyboardNav(bar);
if (tabs.length > 0) switchTab(tabs[0].key);
}
function wireTablistKeyboardNav(bar) {
bar.addEventListener("keydown", function (event) {
var target = event.target;
if (!target || target.getAttribute("role") !== "tab") return;
var buttons = Array.prototype.slice.call(
bar.querySelectorAll("button[role=\"tab\"]")
);
if (buttons.length === 0) return;
var idx = buttons.indexOf(target);
if (idx < 0) return;
var next = -1;
switch (event.key) {
case "ArrowRight":
case "ArrowDown":
next = (idx + 1) % buttons.length;
break;
case "ArrowLeft":
case "ArrowUp":
next = (idx - 1 + buttons.length) % buttons.length;
break;
case "Home":
next = 0;
break;
case "End":
next = buttons.length - 1;
break;
case "Enter":
case " ":
event.preventDefault();
switchTab(target.dataset.tab, true);
return;
default:
return;
}
event.preventDefault();
var targetBtn = buttons[next];
if (targetBtn) {
targetBtn.focus();
switchTab(targetBtn.dataset.tab, true);
}
});
}
var currentTab = null;
function switchTab(key, fromKeyboard) {
if (typeof clearAllSearchFilters === "function") clearAllSearchFilters();
currentTab = key;
document.querySelectorAll(".ps-tab").forEach(function (btn) {
var active = btn.dataset.tab === key;
btn.classList.toggle("active", active);
setAttr(btn, "aria-selected", active ? "true" : "false");
setAttr(btn, "tabindex", active ? "0" : "-1");
});
tabs.forEach(function (t) {
var panel = document.getElementById("panel-" + t.key);
if (panel) panel.style.display = t.key === key ? "" : "none";
});
if (fromKeyboard) {
var active = document.getElementById("tab-" + key);
if (active && typeof active.focus === "function") {
try { active.focus(); } catch { }
}
}
scheduleHashWrite();
}
function countBySeverity() {
var c = { critical: 0, warning: 0, info: 0 };
findings.forEach(function (f) { if (c[f.severity] !== undefined) c[f.severity] += 1; });
return c;
}
function renderFindingsMetrics() {
var root = document.getElementById("findings-metrics");
var sev = countBySeverity();
var total = findings.length;
root.appendChild(metricCard(
"Findings",
String(total),
sev.critical + " crit . " + sev.warning + " warn . " + sev.info + " info"
));
var avoidable = greenSummary ? (greenSummary.avoidable_io_ops || 0) : 0;
var ratio = greenSummary ? Math.round((greenSummary.io_waste_ratio || 0) * 100) : 0;
root.appendChild(metricCard(
"Avoidable I/O",
String(avoidable),
ratio + "% waste ratio"
));
if (hasGreen) {
var co2 = greenSummary.co2;
root.appendChild(metricCard(
"CO2 estimate",
formatGco2(co2.total.mid),
String(co2.total.model || "")
));
}
var offender = greenSummary && greenSummary.top_offenders && greenSummary.top_offenders[0];
if (offender) {
root.appendChild(metricCard(
"Top offender",
truncate(offender.service, 24),
"IIS " + (offender.io_intensity_score || 0).toFixed(1),
true
));
}
}
var filterState = { severity: null, service: null };
function renderFilterChips() {
var root = document.getElementById("findings-filters");
root.textContent = "";
var severityGroup = el("span", "ps-chip-group", null);
setAttr(severityGroup, "role", "radiogroup");
setAttr(severityGroup, "aria-label", "Finding severity");
root.appendChild(severityGroup);
var serviceGroup = el("span", "ps-chip-group", null);
setAttr(serviceGroup, "role", "group");
setAttr(serviceGroup, "aria-label", "Finding service");
root.appendChild(serviceGroup);
function appendChip(parent, label, key, chipRole, stateAttr) {
var btn = el("button", "ps-chip", label);
btn.type = "button";
setAttr(btn, "role", chipRole);
setAttr(btn, "data-key", key);
setAttr(btn, stateAttr, "false");
btn.addEventListener("click", function () { onFilterClick(key); });
parent.appendChild(btn);
}
appendChip(severityGroup, "All", "all", "radio", "aria-checked");
var hasCrit = findings.some(function (f) { return f.severity === "critical"; });
var hasWarn = findings.some(function (f) { return f.severity === "warning"; });
if (hasCrit) appendChip(severityGroup, "Critical", "sev:critical", "radio", "aria-checked");
if (hasWarn) appendChip(severityGroup, "Warning", "sev:warning", "radio", "aria-checked");
var services = Object.create(null);
findings.forEach(function (f) { services[f.service] = true; });
Object.keys(services).sort().forEach(function (s) {
appendChip(serviceGroup, s, "svc:" + s, "button", "aria-pressed");
});
syncFilterChips();
}
function onFilterClick(key) {
if (key === "all") {
filterState.severity = null;
filterState.service = null;
} else if (key.startsWith("sev:")) {
var sv = key.slice(4);
filterState.severity = filterState.severity === sv ? null : sv;
} else if (key.startsWith("svc:")) {
var svc = key.slice(4);
filterState.service = filterState.service === svc ? null : svc;
}
syncFilterChips();
resetShownCount();
renderFindingsList();
scheduleHashWrite();
}
function syncFilterChips() {
var anyActive = !!(filterState.severity || filterState.service);
document.querySelectorAll("#findings-filters .ps-chip").forEach(function (chip) {
var key = chip.dataset.key;
var active = false;
if (key === "all") active = !anyActive;
else if (key === "sev:" + filterState.severity) active = true;
else if (key === "svc:" + filterState.service) active = true;
chip.classList.toggle("active", active);
var role = chip.getAttribute("role");
var stateAttr = role === "radio" ? "aria-checked" : "aria-pressed";
setAttr(chip, stateAttr, active ? "true" : "false");
});
}
var LIST_CAP = 500;
var visibleFindings = [];
var selectedIndex = -1;
var shownCount = LIST_CAP;
function applyFilters() {
return findings.filter(function (f) {
if (filterState.severity && f.severity !== filterState.severity) return false;
if (filterState.service && f.service !== filterState.service) return false;
return true;
});
}
function renderFindingsList() {
var root = document.getElementById("findings-list");
var empty = document.getElementById("findings-empty");
root.textContent = "";
var filtered = applyFilters();
visibleFindings = filtered.slice(0, shownCount);
selectedIndex = visibleFindings.length > 0 ? 0 : -1;
if (visibleFindings.length === 0) {
empty.style.display = "";
syncShowMore(filtered.length);
return;
}
empty.style.display = "none";
visibleFindings.forEach(function (f, idx) {
root.appendChild(buildFindingRow(f, idx));
});
syncSelection();
syncShowMore(filtered.length);
}
function syncShowMore(totalMatching) {
var btn = document.getElementById("findings-show-more");
if (!btn) return;
if (visibleFindings.length >= totalMatching) {
btn.style.display = "none";
return;
}
var remaining = totalMatching - visibleFindings.length;
var next = Math.min(LIST_CAP, remaining);
btn.style.display = "";
btn.textContent = "Show " + next + " more findings (" + remaining + " remaining)";
}
function resetShownCount() { shownCount = LIST_CAP; }
function buildFindingRow(f, idx) {
var row = el("div", "ps-row");
setAttr(row, "data-idx", idx);
var sev = el("div", SEV_CLASS[f.severity] || "ps-sev-info", SEV_LABEL[f.severity] || "INFO");
row.appendChild(sev);
var main = el("div", "ps-fin-main");
var typeRow = el("div", "ps-fin-type");
typeRow.appendChild(document.createTextNode(typeLabel(f.type || f.finding_type)));
var svc = el("span", "ps-fin-service", " in " + (f.service || "-"));
typeRow.appendChild(svc);
main.appendChild(typeRow);
var tpl = (f.pattern && f.pattern.template) || "";
main.appendChild(el("div", "ps-fin-detail", tpl));
row.appendChild(main);
var right = el("div", "ps-fin-right");
var occ = (f.pattern && f.pattern.occurrences) || 0;
right.appendChild(document.createTextNode(occ + " occ"));
right.appendChild(document.createElement("br"));
right.appendChild(el("span", "ps-fin-right-sub", truncate(f.source_endpoint || "-", 48)));
row.appendChild(right);
row.addEventListener("click", function () {
selectedIndex = idx;
syncSelection();
openExplain(f);
});
return row;
}
function syncSelection() {
document.querySelectorAll("#findings-list .ps-row").forEach(function (row, idx) {
row.classList.toggle("selected", idx === selectedIndex);
});
}
function renderExplainEmpty(message) {
document.getElementById("explain-empty").style.display = "none";
var content = document.getElementById("explain-content");
var notEmbedded = document.getElementById("explain-not-embedded");
content.style.display = "none";
notEmbedded.style.display = "";
notEmbedded.textContent = message;
switchTab("explain");
}
function openExplain(f) {
document.getElementById("explain-empty").style.display = "none";
var content = document.getElementById("explain-content");
var notEmbedded = document.getElementById("explain-not-embedded");
notEmbedded.style.display = "none";
notEmbedded.textContent = "";
var trace = tracesById[f.trace_id];
if (!trace) {
renderExplainEmpty(
"Trace not embedded (cap reached). Rerun with --max-traces-embedded <higher> to include it."
);
return;
}
content.style.display = "";
var breadcrumb = document.getElementById("explain-breadcrumb");
var shortTraceId = (f.trace_id || "").slice(0, 12);
breadcrumb.textContent =
"trace_id = " + shortTraceId + " . " + (f.service || "-") + " . " + (f.source_endpoint || "-");
var treeRoot = document.getElementById("explain-tree");
treeRoot.textContent = "";
renderTree(treeRoot, trace, f);
var fix = document.getElementById("explain-fix");
fix.textContent = "";
if (f.suggested_fix) {
fix.style.display = "";
renderSuggestedFix(fix, f.suggested_fix);
} else {
fix.style.display = "none";
}
switchTab("explain");
}
function buildSpanTree(spans) {
var byId = Object.create(null);
spans.forEach(function (s) { byId[s.span_id] = s; });
var childrenByParent = Object.create(null);
var roots = [];
spans.forEach(function (s, idx) {
var parent = s.parent_span_id;
if (parent && byId[parent]) {
if (!childrenByParent[parent]) childrenByParent[parent] = [];
childrenByParent[parent].push(idx);
} else {
roots.push(idx);
}
});
return { roots: roots, childrenByParent: childrenByParent };
}
function emitSpanTreeRows(root, spans, tree, targetTpl) {
var stack = [];
for (var r = tree.roots.length - 1; r >= 0; r--) {
stack.push({ idx: tree.roots[r], depth: 0 });
}
var emitted = 0;
var HARD_SPAN_CAP = 2000;
var MAX_DEPTH = 64;
while (stack.length > 0 && emitted < HARD_SPAN_CAP) {
var frame = stack.pop();
if (frame.depth > MAX_DEPTH) continue;
var span = spans[frame.idx];
root.appendChild(buildSpanRow(span, frame.depth, targetTpl));
emitted += 1;
var kids = tree.childrenByParent[span.span_id] || [];
for (var k = kids.length - 1; k >= 0; k--) {
stack.push({ idx: kids[k], depth: frame.depth + 1 });
}
}
}
function renderTree(root, trace, finding) {
var spans = trace.spans || [];
if (spans.length === 0) {
root.appendChild(el("div", "ps-span dim", "(empty trace)"));
return;
}
var tree = buildSpanTree(spans);
var targetTpl = (finding.pattern && finding.pattern.template) || null;
emitSpanTreeRows(root, spans, tree, targetTpl);
}
function buildSpanRow(span, depth, targetTpl) {
var row = el("div", "ps-span");
var isFinding = targetTpl && span.template === targetTpl;
if (isFinding) row.classList.add("hilite");
else row.classList.add("dim");
row.style.paddingLeft = (depth * 20) + "px";
var pgStatMatch =
hasPgStat && span.event_type === "sql" && pgStatByTemplate[span.template]
? span.template
: null;
if (pgStatMatch) {
row.classList.add("ps-span-pgstat-link");
row.addEventListener("click", function () {
switchTab("pgstat");
showPgStatDrill(pgStatMatch);
});
}
var text = el("span", "ps-span-text");
if (isFinding) text.classList.add("ps-span-find");
text.textContent = describeSpan(span);
row.appendChild(text);
row.appendChild(el("span", "ps-span-dur", formatDurationUs(span.duration_us)));
return row;
}
function describeSpan(span) {
if (span.event_type === "sql") {
return span.template || span.target || span.operation || "(sql)";
}
if (span.event_type === "http_out") {
var op = (span.operation || "GET").toUpperCase();
return op + " " + (span.template || span.target || "");
}
return span.operation || span.target || "(span)";
}
function renderSuggestedFix(root, fix) {
var label = el("div", "ps-drill-label", "Suggested fix . " + (fix.framework || "-"));
root.appendChild(label);
root.appendChild(document.createTextNode(fix.recommendation || ""));
var href = safeHttpsHref(fix.reference_url);
if (href) {
root.appendChild(document.createTextNode(" "));
var a = el("a", "ps-drill-link", "See documentation");
setAttr(a, "href", href);
setAttr(a, "target", "_blank");
setAttr(a, "rel", "noopener noreferrer");
root.appendChild(a);
}
}
function renderGreenPanel() {
if (!hasGreen) return;
var co2 = greenSummary.co2;
var metrics = document.getElementById("green-metrics");
metrics.appendChild(metricCard(
"Operational CO2",
formatGco2(co2.operational_gco2),
"low " + formatGco2(co2.total.low) + " . high " + formatGco2(co2.total.high)
));
metrics.appendChild(metricCard(
"Embodied CO2",
formatGco2(co2.embodied_gco2),
"SCI v1.0 M term"
));
var avoidMid = co2.avoidable.mid;
var ratio = percent(avoidMid, co2.operational_gco2);
metrics.appendChild(metricCard(
"Avoidable",
formatGco2(avoidMid),
ratio + " of operational"
));
var regions = greenSummary.regions || [];
var header = document.getElementById("green-regions-header");
header.textContent = "Regions (" + regions.length + ")";
if (regions.length === 0) {
document.getElementById("green-regions-empty").style.display = "";
return;
}
document.getElementById("green-regions-table").style.display = "";
var tbody = document.getElementById("green-regions-body");
regions.forEach(function (r) {
var tr = document.createElement("tr");
tr.appendChild(el("td", null, r.region || "-"));
tr.appendChild(el("td", null, (r.grid_intensity_gco2_kwh || 0).toFixed(0)));
tr.appendChild(el("td", null, String(r.io_ops || 0)));
tr.appendChild(el("td", null, formatGco2(r.co2_gco2 || 0)));
tr.appendChild(el("td", null, r.intensity_source || "-"));
tbody.appendChild(tr);
});
}
var pgStat = payload.pg_stat || null;
var pgStatByTemplate = Object.create(null);
if (pgStat && Array.isArray(pgStat.rankings)) {
pgStat.rankings.forEach(function (r) {
(r.entries || []).forEach(function (entry) {
pgStatByTemplate[entry.normalized_template] = entry;
});
});
}
var hasPgStat = Object.keys(pgStatByTemplate).length > 0;
var diffData = payload.diff || null;
var hasDiff = !!diffData;
var correlations =
report.correlations && report.correlations.length > 0 ? report.correlations : [];
var hasCorrelations = correlations.length > 0;
var pgStatRankings =
pgStat && Array.isArray(pgStat.rankings) ? pgStat.rankings : [];
var activeRankingIndex = 0;
var pgStatHiliteTemplate = null;
var PGSTAT_LABELS = {
"top by total_exec_time": "Total time",
"top by calls": "Calls",
"top by mean_exec_time": "Mean time",
"top by shared_blks_total": "I/O blocks"
};
function activePgStatEntries() {
if (pgStatRankings.length === 0) return [];
var idx = Math.min(activeRankingIndex, pgStatRankings.length - 1);
return pgStatRankings[idx].entries || [];
}
function renderPgStatPanel() {
if (!hasPgStat) {
document.getElementById("pgstat-empty").style.display = "";
return;
}
renderPgStatRankings();
renderPgStatTable();
}
function renderPgStatRankings() {
var root = document.getElementById("pgstat-rankings");
root.textContent = "";
if (pgStatRankings.length <= 1) {
root.style.display = "none";
return;
}
root.style.display = "";
setAttr(root, "role", "radiogroup");
setAttr(root, "aria-label", "pg_stat ranking");
pgStatRankings.forEach(function (r, idx) {
var label = PGSTAT_LABELS[r.label] || r.label || ("Ranking " + (idx + 1));
var btn = el("button", "ps-chip", label);
btn.type = "button";
setAttr(btn, "role", "radio");
setAttr(btn, "data-ranking-index", String(idx));
var isActive = idx === activeRankingIndex;
setAttr(btn, "aria-checked", isActive ? "true" : "false");
if (isActive) btn.classList.add("active");
btn.addEventListener("click", function () {
selectPgStatRanking(idx);
});
root.appendChild(btn);
});
}
function selectPgStatRanking(idx) {
sessionSet(STORAGE_KEYS.pgstatRanking, rankingSlugForIndex(idx));
if (idx === activeRankingIndex) return;
activeRankingIndex = idx;
var chips = document.querySelectorAll("#pgstat-rankings .ps-chip");
for (var i = 0; i < chips.length; i++) {
var isActive = i === idx;
chips[i].classList.toggle("active", isActive);
setAttr(chips[i], "aria-checked", isActive ? "true" : "false");
}
renderPgStatTable();
scheduleHashWrite();
if (pgStatHiliteTemplate) {
var stillPresent = activePgStatEntries().some(function (entry) {
return entry.normalized_template === pgStatHiliteTemplate;
});
if (stillPresent) {
highlightPgStatRow(pgStatHiliteTemplate);
} else {
clearPgStatDrill();
}
}
if (SEARCHABLE_TABS && SEARCHABLE_TABS.pgstat) {
applySearchFilter(SEARCHABLE_TABS.pgstat);
}
}
function renderPgStatTable() {
var body = document.getElementById("pgstat-body");
body.textContent = "";
activePgStatEntries().forEach(function (entry) {
var tr = document.createElement("tr");
setAttr(tr, "data-template", entry.normalized_template);
tr.appendChild(el("td", null, entry.normalized_template));
tr.appendChild(el("td", null, String(entry.calls || 0)));
tr.appendChild(el("td", null, (entry.total_exec_time_ms || 0).toFixed(1)));
tr.appendChild(el("td", null, (entry.mean_exec_time_ms || 0).toFixed(2)));
body.appendChild(tr);
});
}
function showPgStatDrill(template) {
var drill = document.getElementById("pgstat-drill");
var txt = document.getElementById("pgstat-drill-text");
drill.style.display = "";
txt.textContent = template;
pgStatHiliteTemplate = template;
highlightPgStatRow(template);
}
function clearPgStatDrill() {
document.getElementById("pgstat-drill").style.display = "none";
pgStatHiliteTemplate = null;
document.querySelectorAll("#pgstat-body tr").forEach(function (row) {
row.classList.remove("hilite");
});
}
function highlightPgStatRow(template) {
var matched = null;
document.querySelectorAll("#pgstat-body tr").forEach(function (row) {
if (row.dataset.template === template) {
row.classList.add("hilite");
matched = row;
} else {
row.classList.remove("hilite");
}
});
if (matched) matched.scrollIntoView({ block: "nearest" });
}
function renderDiffPanel() {
if (!hasDiff) return;
renderDiffFindingsList(
"diff-new-list",
"diff-new-header",
"New findings",
diffData.new_findings || [],
true
);
renderDiffFindingsList(
"diff-resolved-list",
"diff-resolved-header",
"Resolved findings",
diffData.resolved_findings || [],
"resolved"
);
renderDiffSevTable(diffData.severity_changes || []);
renderDiffEndpTable(diffData.endpoint_metric_deltas || []);
}
function renderDiffFindingsList(listId, headerId, label, items, kind) {
var list = document.getElementById(listId);
var hdr = document.getElementById(headerId);
hdr.textContent = label + " (" + items.length + ")";
list.textContent = "";
items.forEach(function (f) {
list.appendChild(buildDiffFindingRow(f, kind));
});
}
function buildDiffFindingRow(f, kind) {
var row = el("div", "ps-row");
row.appendChild(
el("div", SEV_CLASS[f.severity] || "ps-sev-info", SEV_LABEL[f.severity] || "INFO")
);
var main = el("div", "ps-fin-main");
var typeRow = el("div", "ps-fin-type");
typeRow.appendChild(document.createTextNode(typeLabel(f.type || f.finding_type)));
typeRow.appendChild(el("span", "ps-fin-service", " in " + (f.service || "-")));
main.appendChild(typeRow);
main.appendChild(el("div", "ps-fin-detail", (f.pattern && f.pattern.template) || ""));
row.appendChild(main);
var right = el("div", "ps-fin-right");
right.appendChild(document.createTextNode(((f.pattern && f.pattern.occurrences) || 0) + " occ"));
right.appendChild(document.createElement("br"));
right.appendChild(el("span", "ps-fin-right-sub", truncate(f.source_endpoint || "-", 48)));
row.appendChild(right);
if (kind === "resolved") {
row.addEventListener("click", function () {
renderExplainEmpty(
"This finding was resolved. Its trace lives in the baseline report, not in the current run. Open the baseline report separately to explore its traces."
);
});
} else if (kind === true || kind === "new") {
row.addEventListener("click", function () {
openExplain(f);
});
} else {
row.style.cursor = "default";
}
return row;
}
function renderDiffSevTable(items) {
var hdr = document.getElementById("diff-sev-header");
var tbl = document.getElementById("diff-sev-table");
var body = document.getElementById("diff-sev-body");
hdr.textContent = "Severity changes (" + items.length + ")";
body.textContent = "";
if (items.length === 0) {
tbl.style.display = "none";
return;
}
tbl.style.display = "";
items.forEach(function (c) {
var f = c.finding || {};
var tr = document.createElement("tr");
tr.appendChild(el("td", null, typeLabel(f.type || f.finding_type)));
tr.appendChild(el("td", null, f.service || "-"));
tr.appendChild(el("td", null, f.source_endpoint || "-"));
tr.appendChild(el("td", null, SEV_LABEL[c.before_severity] || String(c.before_severity)));
tr.appendChild(el("td", null, SEV_LABEL[c.after_severity] || String(c.after_severity)));
body.appendChild(tr);
});
}
function renderDiffEndpTable(items) {
var hdr = document.getElementById("diff-endp-header");
var tbl = document.getElementById("diff-endp-table");
var body = document.getElementById("diff-endp-body");
hdr.textContent = "Endpoint metric deltas (" + items.length + ")";
body.textContent = "";
if (items.length === 0) {
tbl.style.display = "none";
return;
}
tbl.style.display = "";
items.forEach(function (d) {
var tr = document.createElement("tr");
tr.appendChild(el("td", null, d.service || "-"));
tr.appendChild(el("td", null, d.endpoint || "-"));
tr.appendChild(el("td", null, String(d.before_io_ops || 0)));
tr.appendChild(el("td", null, String(d.after_io_ops || 0)));
var delta = d.delta || 0;
tr.appendChild(el("td", null, (delta > 0 ? "+" : "") + String(delta)));
body.appendChild(tr);
});
}
function renderCorrelationsPanel() {
if (!hasCorrelations) return;
var list = document.getElementById("correlations-list");
list.textContent = "";
correlations.forEach(function (c) {
var src = c.source || {};
var tgt = c.target || {};
var row = el("div", "ps-row ps-correlation-row");
var leftLabel =
(src.service || "?") + ":" + typeLabel(src.finding_type || "?");
var rightLabel =
(tgt.service || "?") + ":" + typeLabel(tgt.finding_type || "?");
var main = el("div", "ps-fin-main");
main.appendChild(el("div", "ps-fin-type", leftLabel + " -> " + rightLabel));
var conf = Math.round((c.confidence || 0) * 100);
var lag = (c.median_lag_ms || 0).toFixed(0);
main.appendChild(
el(
"div",
"ps-fin-detail",
"confidence " + conf + "% . median lag " + lag + " ms"
)
);
row.appendChild(el("div", "ps-sev-info", "CORR"));
row.appendChild(main);
row.appendChild(
el("div", "ps-fin-right", String(c.co_occurrence_count || 0) + "x")
);
if (c.sample_trace_id) {
row.classList.add("ps-correlation-clickable");
row.addEventListener("click", function () {
openExplain(syntheticFindingFromCorrelation(c));
});
} else {
row.style.cursor = "default";
}
list.appendChild(row);
});
}
function syntheticFindingFromCorrelation(c) {
var tgt = c.target || {};
return {
trace_id: c.sample_trace_id,
service: tgt.service || "-",
source_endpoint: "-",
pattern: { template: tgt.template || "" }
};
}
function matchRowByText(row, q) {
return (row.textContent || "").toLowerCase().includes(q);
}
var SEARCHABLE_TABS = {
findings: {
inputId: "findings-search",
rowsSelector: "#findings-list .ps-row"
},
pgstat: {
inputId: "pgstat-search",
rowsSelector: "#pgstat-body tr"
},
diff: {
inputId: "diff-search",
rowsSelector: "#diff-new-list .ps-row, #diff-resolved-list .ps-row"
},
correlations: {
inputId: "correlations-search",
rowsSelector: "#correlations-list .ps-row"
}
};
function initSearchInputs() {
Object.keys(SEARCHABLE_TABS).forEach(function (tabKey) {
var cfg = SEARCHABLE_TABS[tabKey];
var input = document.getElementById(cfg.inputId);
if (!input) return;
input.addEventListener("input", function () {
applySearchFilter(cfg);
scheduleHashWrite();
});
});
}
function applySearchFilter(cfg) {
var input = document.getElementById(cfg.inputId);
var q = (input.value || "").toLowerCase();
document.querySelectorAll(cfg.rowsSelector).forEach(function (row) {
row.style.display = !q || matchRowByText(row, q) ? "" : "none";
});
}
function openSearchForActiveTab() {
var cfg = SEARCHABLE_TABS[currentTab];
if (!cfg) return;
var input = document.getElementById(cfg.inputId);
if (!input) return;
input.style.display = "block";
input.focus();
input.select();
}
function closeSearch(input) {
input.value = "";
applySearchFilter(SEARCHABLE_TABS[currentTab]);
input.style.display = "none";
input.blur();
}
function clearAllSearchFilters() {
Object.keys(SEARCHABLE_TABS).forEach(function (tabKey) {
var cfg = SEARCHABLE_TABS[tabKey];
var input = document.getElementById(cfg.inputId);
if (!input) return;
if (input.value) {
input.value = "";
applySearchFilter(cfg);
}
input.style.display = "none";
});
}
function renderTrimBanner() {
var t = payload.trimmed_traces;
if (!t) return;
var b = document.getElementById("trim-banner");
b.style.display = "";
b.textContent = "Showing " + t.kept + " of " + t.total +
" traces (trimmed for file size). Rerun with --max-traces-embedded <higher> to see more.";
}
document.getElementById("theme-toggle").addEventListener("click", function () {
var idx = THEME_MODES.indexOf(currentThemeMode());
var next = THEME_MODES[(idx + 1) % THEME_MODES.length];
sessionSet(STORAGE_KEYS.theme, next);
applyTheme(next);
});
function csvEscape(value) {
if (value === null || value === undefined) return "";
var s = String(value);
if (s.length > 0 && "=+-@\t".includes(s.charAt(0))) {
s = "'" + s;
}
if (s.includes(",") || s.includes("\"") ||
s.includes("\n") || s.includes("\r")) {
return "\"" + s.replaceAll("\"", "\"\"") + "\"";
}
return s;
}
function csvLine(cells) {
return cells.map(csvEscape).join(",");
}
function buildCsv(header, rows) {
var lines = [csvLine(header)];
rows.forEach(function (r) { lines.push(csvLine(r)); });
return lines.join("\r\n") + "\r\n";
}
function sanitizeLabelForFilename(s) {
if (!s) return "";
var cleaned = String(s).replaceAll(/[^A-Za-z0-9]+/g, "-");
cleaned = cleaned.replaceAll(/-+/g, "-").replaceAll(/^-|-$/g, "");
return cleaned;
}
function pad2(n) { return n < 10 ? "0" + n : String(n); }
function timestampForFilename() {
var d = new Date();
return d.getFullYear() +
pad2(d.getMonth() + 1) +
pad2(d.getDate()) + "-" +
pad2(d.getHours()) +
pad2(d.getMinutes()) +
pad2(d.getSeconds());
}
function buildCsvFilename(tabName) {
var label = sanitizeLabelForFilename(payload.input_label || "");
var prefix = label ? "perf-sentinel-" + label : "perf-sentinel";
return prefix + "-" + tabName + "-" + timestampForFilename() + ".csv";
}
function downloadCsv(tabName, csvText) {
var blob = new Blob([csvText], { type: "text/csv;charset=utf-8" });
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.download = buildCsvFilename(tabName);
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(function () { URL.revokeObjectURL(url); }, 0);
}
function searchTermFor(tabKey) {
var cfg = SEARCHABLE_TABS && SEARCHABLE_TABS[tabKey];
if (!cfg) return "";
var input = document.getElementById(cfg.inputId);
if (!input) return "";
return (input.value || "").toLowerCase();
}
function matchesSearch(haystack, term) {
if (!term) return true;
return String(haystack || "").toLowerCase().includes(term);
}
function exportFindingsCsv() {
var term = searchTermFor("findings");
var header = [
"severity", "type", "service", "endpoint", "template",
"occurrences", "first_timestamp", "last_timestamp",
"code_function", "code_filepath", "code_lineno",
"suggested_fix_framework", "suggested_fix_recommendation"
];
var rows = applyFilters().filter(function (f) {
var blob = [
f.severity, f.type || f.finding_type, f.service, f.source_endpoint,
f.pattern && f.pattern.template
].join(" ");
return matchesSearch(blob, term);
}).map(function (f) {
var p = f.pattern || {};
var loc = f.code_location || {};
var fix = f.suggested_fix || {};
return [
f.severity || "",
f.type || f.finding_type || "",
f.service || "",
f.source_endpoint || "",
p.template || "",
p.occurrences ?? "",
f.first_timestamp || "",
f.last_timestamp || "",
loc.function || "",
loc.filepath || "",
loc.lineno ?? "",
fix.framework || "",
fix.recommendation || ""
];
});
downloadCsv("findings", buildCsv(header, rows));
}
function exportPgStatCsv() {
var term = searchTermFor("pgstat");
var header = [
"template", "calls", "total_exec_time_ms", "mean_exec_time_ms",
"rows", "shared_blks_hit", "shared_blks_read", "seen_in_traces"
];
var rows = activePgStatEntries().filter(function (e) {
return matchesSearch(e.normalized_template, term);
}).map(function (e) {
return [
e.normalized_template || "",
e.calls ?? "",
e.total_exec_time_ms ?? "",
e.mean_exec_time_ms ?? "",
e.rows ?? "",
e.shared_blks_hit ?? "",
e.shared_blks_read ?? "",
e.seen_in_traces ? "true" : "false"
];
});
downloadCsv("pgstat", buildCsv(header, rows));
}
function diffFindingMatchBlob(f) {
return [
f.severity, f.type || f.finding_type, f.service, f.source_endpoint,
f.pattern && f.pattern.template
].join(" ");
}
function exportDiffCsv() {
if (!hasDiff) return;
var term = searchTermFor("diff");
var header = [
"section", "severity", "type", "service", "endpoint", "template",
"occurrences", "before_severity", "after_severity",
"before_io_ops", "after_io_ops", "delta"
];
var rows = [];
(diffData.new_findings || []).forEach(function (f) {
if (!matchesSearch(diffFindingMatchBlob(f), term)) return;
var p = f.pattern || {};
rows.push([
"new", f.severity || "", f.type || f.finding_type || "",
f.service || "", f.source_endpoint || "", p.template || "",
p.occurrences ?? "",
"", "", "", "", ""
]);
});
(diffData.resolved_findings || []).forEach(function (f) {
if (!matchesSearch(diffFindingMatchBlob(f), term)) return;
var p = f.pattern || {};
rows.push([
"resolved", f.severity || "", f.type || f.finding_type || "",
f.service || "", f.source_endpoint || "", p.template || "",
p.occurrences ?? "",
"", "", "", "", ""
]);
});
(diffData.severity_changes || []).forEach(function (c) {
var f = c.finding || {};
if (!matchesSearch(diffFindingMatchBlob(f), term)) return;
var p = f.pattern || {};
rows.push([
"severity_change", "", f.type || f.finding_type || "",
f.service || "", f.source_endpoint || "", p.template || "",
p.occurrences ?? "",
c.before_severity || "", c.after_severity || "",
"", "", ""
]);
});
(diffData.endpoint_metric_deltas || []).forEach(function (d) {
var blob = [d.service, d.endpoint].join(" ");
if (!matchesSearch(blob, term)) return;
rows.push([
"endpoint_delta", "", "",
d.service || "", d.endpoint || "", "",
"", "", "",
d.before_io_ops ?? "",
d.after_io_ops ?? "",
d.delta ?? ""
]);
});
downloadCsv("diff", buildCsv(header, rows));
}
function exportCorrelationsCsv() {
var term = searchTermFor("correlations");
var header = [
"service_a", "type_a", "template_a",
"service_b", "type_b", "template_b",
"confidence", "co_occurrence_count",
"source_total_occurrences", "median_lag_ms",
"first_seen", "last_seen"
];
var rows = correlations.filter(function (c) {
var src = c.source || {};
var tgt = c.target || {};
var blob = [
src.service, src.finding_type, tgt.service, tgt.finding_type
].join(" ");
return matchesSearch(blob, term);
}).map(function (c) {
var s = c.source || {};
var t = c.target || {};
return [
s.service || "", s.finding_type || "", s.template || "",
t.service || "", t.finding_type || "", t.template || "",
c.confidence ?? "",
c.co_occurrence_count ?? "",
c.source_total_occurrences ?? "",
c.median_lag_ms ?? "",
c.first_seen || "", c.last_seen || ""
];
});
downloadCsv("correlations", buildCsv(header, rows));
}
var RANKING_SLUGS = ["total_time", "calls", "mean_time", "io_blocks"];
function rankingSlugForIndex(idx) {
return RANKING_SLUGS[idx] || String(idx);
}
function rankingIndexForSlug(slug) {
if (RANKING_SLUGS.includes(slug)) return RANKING_SLUGS.indexOf(slug);
var n = Number.parseInt(slug, 10);
return Number.isFinite(n) ? n : -1;
}
var HASH_KEYS = new Set(["search", "ranking", "severity", "service"]);
function readHash() {
var raw = (location.hash || "").replace(/^#/, "");
if (!raw) return null;
var parts = raw.split("&");
var state = { tab: parts.shift() || null };
parts.forEach(function (p) {
if (!p) return;
var eq = p.indexOf("=");
if (eq < 0) return;
var k = p.slice(0, eq);
if (!HASH_KEYS.has(k)) return;
var v = p.slice(eq + 1);
try {
v = decodeURIComponent(v);
} catch {
return;
}
state[k] = v;
});
return state;
}
function currentStateForHash() {
var state = { tab: currentTab };
if (currentTab && SEARCHABLE_TABS[currentTab]) {
var term = searchTermFor(currentTab);
if (term) state.search = term;
}
if (currentTab === "findings") {
if (filterState.severity) state.severity = filterState.severity;
if (filterState.service) state.service = filterState.service;
}
if (currentTab === "pgstat") {
state.ranking = rankingSlugForIndex(activeRankingIndex);
}
return state;
}
function encodeHashFromState(state) {
if (!state || !state.tab) return "";
var parts = [state.tab];
["search", "ranking", "severity", "service"].forEach(function (k) {
var v = state[k];
if (v !== null && v !== undefined && v !== "") {
parts.push(k + "=" + encodeURIComponent(String(v)));
}
});
return parts.join("&");
}
var _lastWrittenHash = null;
function writeHash(state) {
var hashStr = encodeHashFromState(state);
var full = hashStr ? "#" + hashStr : "";
try {
history.replaceState(null, "", full || location.pathname + location.search);
_lastWrittenHash = hashStr;
} catch {
location.hash = hashStr;
_lastWrittenHash = hashStr;
}
}
var hashWriteTimer = null;
function scheduleHashWrite() {
if (hashWriteTimer) clearTimeout(hashWriteTimer);
hashWriteTimer = setTimeout(function () {
hashWriteTimer = null;
writeHash(currentStateForHash());
}, 200);
}
function applyHash(state) {
if (!state || !state.tab) return false;
if (!tabIsRegistered(state.tab)) return false;
if (state.tab === "findings") {
filterState.severity = null;
filterState.service = null;
if (state.severity === "critical" || state.severity === "warning") {
filterState.severity = state.severity;
}
if (state.service) filterState.service = state.service;
syncFilterChips();
resetShownCount();
renderFindingsList();
}
if (state.tab === "pgstat") {
var idx = rankingIndexForSlug(state.ranking || "");
if (idx >= 0 && idx < pgStatRankings.length) {
selectPgStatRanking(idx);
}
}
switchTab(state.tab);
if (state.search && SEARCHABLE_TABS[state.tab]) {
var cfg = SEARCHABLE_TABS[state.tab];
var input = document.getElementById(cfg.inputId);
if (input) {
input.style.display = "block";
input.value = state.search;
applySearchFilter(cfg);
}
}
return true;
}
var CHEATSHEET_ROWS = [
["j", "Move finding selection down"],
["k", "Move finding selection up"],
["Enter", "Open selected finding in Explain"],
["Esc", "Close help, close search, clear filters, back from Explain"],
["/", "Open filter search for active tab"],
["g f", "Go to Findings"],
["g e", "Go to Explain (if available)"],
["g p", "Go to pg_stat (if available)"],
["g d", "Go to Diff (if available)"],
["g c", "Go to Correlations (if available)"],
["g r", "Go to GreenOps (if available)"],
["?", "Show this cheatsheet"]
];
function populateCheatsheet() {
var body = document.getElementById("cheatsheet-body");
if (!body) return;
body.textContent = "";
CHEATSHEET_ROWS.forEach(function (row) {
var tr = document.createElement("tr");
var tdK = document.createElement("td");
var kbd = el("span", "ps-kbd", row[0]);
tdK.appendChild(kbd);
tr.appendChild(tdK);
tr.appendChild(el("td", null, row[1]));
body.appendChild(tr);
});
}
function isCheatsheetOpen() {
var dlg = document.getElementById("cheatsheet");
return !!(dlg && dlg.open);
}
var cheatsheetPriorFocus = null;
function openCheatsheet() {
var dlg = document.getElementById("cheatsheet");
if (!dlg || dlg.open) return;
cheatsheetPriorFocus = document.activeElement;
dlg.showModal();
var closeBtn = document.getElementById("cheatsheet-close");
if (closeBtn) closeBtn.focus();
}
function closeCheatsheet() {
var dlg = document.getElementById("cheatsheet");
if (dlg && dlg.open) dlg.close();
}
function toggleCheatsheet() {
if (isCheatsheetOpen()) closeCheatsheet();
else openCheatsheet();
}
function anyFilterChipActive() {
return !!(filterState.severity || filterState.service);
}
function clearFilterChips() {
filterState.severity = null;
filterState.service = null;
syncFilterChips();
resetShownCount();
renderFindingsList();
}
function isSearchInputFocused() {
var active = document.activeElement;
return !!(active && active.tagName === "INPUT" && active.classList.contains("ps-search"));
}
function isTextInputFocused() {
var a = document.activeElement;
if (!a) return false;
var tag = a.tagName;
if (tag === "INPUT" || tag === "TEXTAREA") return true;
return a.isContentEditable === true;
}
function handleEscape(ev) {
if (isCheatsheetOpen()) {
closeCheatsheet();
ev.preventDefault();
return;
}
if (isSearchInputFocused()) {
closeSearch(document.activeElement);
ev.preventDefault();
return;
}
if (currentTab === "explain") {
switchTab("findings");
return;
}
if (currentTab === "findings" && anyFilterChipActive()) {
clearFilterChips();
scheduleHashWrite();
ev.preventDefault();
}
}
function handleSlash(ev) {
if (isSearchInputFocused() || !SEARCHABLE_TABS[currentTab]) return;
openSearchForActiveTab();
ev.preventDefault();
}
function handleFindingsNav(ev) {
if (visibleFindings.length === 0) return;
if (ev.key === "j") {
selectedIndex = (selectedIndex + 1) % visibleFindings.length;
syncSelection();
scrollSelectedIntoView();
} else if (ev.key === "k") {
selectedIndex = (selectedIndex - 1 + visibleFindings.length) % visibleFindings.length;
syncSelection();
scrollSelectedIntoView();
} else if (ev.key === "Enter" && selectedIndex >= 0) {
openExplain(visibleFindings[selectedIndex]);
}
ev.preventDefault();
}
var pendingGTimer = null;
var G_MAPPING = {
f: "findings", e: "explain", p: "pgstat",
d: "diff", c: "correlations", r: "green"
};
function clearPendingG() {
if (pendingGTimer) {
clearTimeout(pendingGTimer);
pendingGTimer = null;
}
}
function handleGPrefix(ev) {
if (ev.repeat) return false;
if (ev.key === "g" && pendingGTimer === null) {
pendingGTimer = setTimeout(function () { pendingGTimer = null; }, 1000);
return true;
}
if (pendingGTimer !== null) {
clearPendingG();
var target = G_MAPPING[ev.key];
if (target && tabIsRegistered(target)) {
switchTab(target);
ev.preventDefault();
}
return true;
}
return false;
}
document.addEventListener("keydown", function (ev) {
if (ev.key === "Escape") { handleEscape(ev); return; }
if (ev.key === "?" && !isTextInputFocused()) {
toggleCheatsheet();
ev.preventDefault();
return;
}
if (isCheatsheetOpen()) return;
if (ev.key === "/") { handleSlash(ev); return; }
if (isSearchInputFocused()) return;
if (isTextInputFocused()) return;
if (handleGPrefix(ev)) return;
if (currentTab !== "findings") return;
if (ev.key === "j" || ev.key === "k" || ev.key === "Enter") {
handleFindingsNav(ev);
}
});
function scrollSelectedIntoView() {
var rows = document.querySelectorAll("#findings-list .ps-row");
if (selectedIndex >= 0 && selectedIndex < rows.length) {
rows[selectedIndex].scrollIntoView({ block: "nearest" });
}
}
document.getElementById("pgstat-drill-clear").addEventListener("click", clearPgStatDrill);
function registerAllTabs() {
registerTab("findings", "Findings", function () { return findings.length; });
registerTab("explain", "Explain", null);
if (hasPgStat) {
registerTab("pgstat", "pg_stat", function () {
return activePgStatEntries().length;
});
}
if (hasDiff) {
registerTab("diff", "Diff", function () {
return (
(diffData.new_findings || []).length + (diffData.resolved_findings || []).length
);
});
}
if (hasCorrelations) {
registerTab("correlations", "Correlations", function () {
return correlations.length;
});
}
if (hasGreen) registerTab("green", "GreenOps", null);
renderTabs();
}
function restorePersistedPgStatRanking() {
if (!hasPgStat) return;
var storedRanking = sessionGet(STORAGE_KEYS.pgstatRanking);
if (!storedRanking) return;
var storedIdx = rankingIndexForSlug(storedRanking);
if (storedIdx >= 0 && storedIdx < pgStatRankings.length) {
activeRankingIndex = storedIdx;
}
}
function renderAllPanels() {
renderTrimBanner();
renderFindingsMetrics();
renderFilterChips();
restorePersistedPgStatRanking();
renderFindingsList();
if (hasPgStat) renderPgStatPanel();
if (hasDiff) renderDiffPanel();
if (hasCorrelations) renderCorrelationsPanel();
if (hasGreen) renderGreenPanel();
}
function wireCheatsheetDialog() {
var closeBtn = document.getElementById("cheatsheet-close");
if (closeBtn) closeBtn.addEventListener("click", closeCheatsheet);
var dlg = document.getElementById("cheatsheet");
if (!dlg) return;
dlg.addEventListener("close", restorePriorFocus);
dlg.addEventListener("click", function (ev) {
if (ev.target === dlg) closeCheatsheet();
});
}
function restorePriorFocus() {
var prior = cheatsheetPriorFocus;
cheatsheetPriorFocus = null;
if (!prior || typeof prior.focus !== "function") return;
try {
prior.focus();
} catch {
return;
}
}
function wireShowMoreButton() {
var btn = document.getElementById("findings-show-more");
if (!btn) return;
btn.addEventListener("click", function () {
shownCount += LIST_CAP;
renderFindingsList();
});
}
function wireExportButtons() {
var exportHandlers = {
findings: exportFindingsCsv,
pgstat: exportPgStatCsv,
diff: exportDiffCsv,
correlations: exportCorrelationsCsv
};
Object.keys(exportHandlers).forEach(function (tabKey) {
var btn = document.getElementById(tabKey + "-export");
if (btn) btn.addEventListener("click", exportHandlers[tabKey]);
});
}
function copyCurrentLink(btn) {
if (!btn.dataset.baseLabel) btn.dataset.baseLabel = btn.textContent;
var baseLabel = btn.dataset.baseLabel;
var href = location.href;
function flashCopied() {
btn.textContent = "Copied";
if (btn._copyFlashTimer) clearTimeout(btn._copyFlashTimer);
btn._copyFlashTimer = setTimeout(function () {
btn._copyFlashTimer = null;
btn.textContent = baseLabel;
}, 1500);
}
function fallbackCopy() {
try {
var ta = document.createElement("textarea");
ta.value = href;
ta.setAttribute("readonly", "");
ta.style.position = "absolute";
ta.style.left = "-9999px";
document.body.appendChild(ta);
ta.select();
var ok = document.execCommand("copy");
ta.remove();
if (ok) {
flashCopied();
} else {
console.error("copyCurrentLink: execCommand('copy') returned false");
}
} catch (err) {
console.error("copyCurrentLink fallback failed", err);
}
}
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
navigator.clipboard.writeText(href).then(flashCopied, function (err) {
console.error("navigator.clipboard.writeText rejected", err);
fallbackCopy();
});
} else {
fallbackCopy();
}
}
function wireCopyLinkButtons() {
["findings", "pgstat", "diff", "correlations"].forEach(function (tabKey) {
var btn = document.getElementById(tabKey + "-copy-link");
if (!btn) return;
btn.addEventListener("click", function () {
copyCurrentLink(btn);
});
});
}
function applyInitialHashOrWrite() {
var initialHash = readHash();
if (applyHash(initialHash)) return;
writeHash(currentStateForHash());
}
function wireHashChangeListener() {
globalThis.addEventListener("hashchange", function () {
var raw = (location.hash || "").replace(/^#/, "");
if (raw === _lastWrittenHash) return;
applyHash(readHash());
});
}
renderTopbar();
registerAllTabs();
renderAllPanels();
initSearchInputs();
populateCheatsheet();
wireCheatsheetDialog();
wireShowMoreButton();
wireExportButtons();
wireCopyLinkButtons();
applyInitialHashOrWrite();
wireHashChangeListener();
})();
</script>
</body>
</html>