<!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="{{CONTENT_SECURITY_POLICY}}" />
<title>{{PAGE_TITLE}}</title>
<script>try{var d=localStorage.getItem('perf-sentinel:density');document.documentElement.setAttribute('data-density',d==='compact'?'compact':'comfort')}catch(e){document.documentElement.setAttribute('data-density','comfort')}</script>
<style>
{{FONT_FACES}}
:root {
--font-ui: 'DM Sans', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, Consolas, monospace;
--font-sans: var(--font-ui);
--r: 16px; --r-sm: 8px;
}
:root[data-theme="dark"] {
--bg: #141a17; --surface: #1c241f; --surface-2: #252e28; --surface-3: #2d3730;
--border: #2b352e; --border-2: #3b473f;
--text: #e8ede9; --text-2: #a3b5ab; --text-3: #7d8c83;
--brand: #0ba671; --brand-2: #2fd49a; --brand-text: #3ecf9d; --accent-bg: rgba(11,166,113,.13);
--crit-fg: #ffa79e; --crit-bg: #3a1f1c; --crit-bd: #573029;
--warn-fg: #f2c08a; --warn-bg: #392a19; --warn-bd: #53381f;
--info-fg: #8fc0f5; --info-bg: #1a2836; --info-bd: #26394e;
--ok-fg: #8fe6b8; --ok-bg: #17342a; --ok-bd: #235c41;
--pill: #1e3b30; --pill-fg: #9fe9c6; --btn-bd: #2e4d41; --btn-fg: #5ecfa6;
--fix-bg: #16382b; --fix-fg: #a5e8c9;
--glow: none;
}
:root[data-theme="light"] {
--bg: #ffffff; --surface: #f7f7f7; --surface-2: #ffffff; --surface-3: #eef1ef;
--border: #ebebeb; --border-2: #dcdcdc;
--text: #20261f; --text-2: #556b5c; --text-3: #6a6a6a;
--brand: #0ba671; --brand-2: #0b523a; --brand-text: #11845d; --accent-bg: #e0f4ed;
--crit-fg: #bf2a22; --crit-bg: #ffe4e1; --crit-bd: #f6d2cd;
--warn-fg: #754618; --warn-bg: #ffe0c2; --warn-bd: #f0d2b2;
--info-fg: #1f5e9e; --info-bg: #e9f0f8; --info-bd: #d5e4f3;
--ok-fg: #1b530d; --ok-bg: #ddf3dc; --ok-bd: #c6e6c4;
--pill: #d6e9e3; --pill-fg: #0b523a; --btn-bd: #a7c7bc; --btn-fg: #186b4f;
--fix-bg: #d6e9e3; --fix-fg: #0b523a;
--glow: none;
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body { background: var(--bg); color: var(--text); font-family: var(--font-ui); font-size: 13.5px; line-height: 1.5; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 6px; }
::-webkit-scrollbar-track { background: transparent; }
input { font-family: inherit; }
code { font-family: var(--font-mono); }
a { color: var(--brand-text); }
a:not(.ps-brand) { text-decoration: underline; text-underline-offset: 2px; transition: background .15s ease; }
a:not(.ps-brand):hover { background: color-mix(in srgb, var(--brand) 16%, transparent); border-radius: 3px; }
[data-sev] { font-family: var(--font-ui); font-weight: 600; letter-spacing: 0; }
[data-sev="crit"] { color: var(--crit-fg); background: var(--crit-bg); border: 1px solid transparent; }
[data-sev="warn"] { color: var(--warn-fg); background: var(--warn-bg); border: 1px solid transparent; }
[data-sev="info"] { color: var(--info-fg); background: var(--info-bg); border: 1px solid transparent; }
[data-sev="ok"] { color: var(--ok-fg); background: var(--ok-bg); border: 1px solid transparent; }
[data-tone="crit"] { color: var(--crit-fg); }
[data-tone="ok"] { color: var(--ok-fg); }
[data-tone="warn"] { color: var(--warn-fg); }
[data-tone="info"] { color: var(--info-fg); }
[data-tone="neutral"] { color: var(--text); }
[data-tone="brand"] { color: var(--brand); }
[data-tone="accent"] { color: var(--brand-text); }
[data-d="0"] { margin-left: 0; } [data-d="1"] { margin-left: 22px; }
[data-d="2"] { margin-left: 44px; } [data-d="3"] { margin-left: 66px; }
.ps-shell { display: grid; grid-template-columns: 252px 1fr; min-height: 100vh; background-image: var(--glow); }
.ps-sidebar { border-right: 0; background: var(--surface); }
.ps-sidebar-sticky { position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; }
.ps-brand-block { padding: 20px 18px 14px; display: flex; flex-direction: column; gap: 7px; align-items: flex-start; }
.ps-brand { display: block; text-decoration: none; }
.ps-logo { display: none; }
.ps-logo svg, .ps-logo img { height: 34px; width: auto; display: block; }
:root[data-theme="dark"] .ps-logo-dark { display: block; }
:root[data-theme="light"] .ps-logo-light { display: block; }
.ps-brand-eyebrow { font-family: var(--font-mono); font-size: 11px; letter-spacing: .6px; color: var(--text-3); padding-left: 2px; }
.ps-nav { display: flex; flex-direction: column; gap: 3px; padding: 10px 12px; flex: 1; overflow: auto; }
.ps-nav-eyebrow { font-family: var(--font-mono); font-size: 11px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--text-3); padding: 10px 9px 6px; }
[data-nav] { appearance: none; font-family: inherit; width: 100%; text-align: left; display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 10px; font-size: 13.5px; font-weight: 500; color: var(--text-2); background: transparent; border: 0; cursor: pointer; transition: background .13s, color .13s; }
[data-nav]:hover { color: var(--text); background: color-mix(in srgb, var(--pill) 50%, transparent); }
[data-nav]:hover > .ps-nav-dot { background: var(--brand); opacity: 1; }
[data-nav][aria-selected="true"] { color: var(--pill-fg); background: var(--pill); font-weight: 600; }
[data-nav][aria-selected="true"] > .ps-nav-dot { background: var(--brand); opacity: 1; }
.ps-nav-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .85; flex: none; }
.ps-nav-label { flex: 1; }
.ps-nav-badge { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); min-width: 16px; display: inline-flex; justify-content: center; flex: none; }
[data-nav].ps-tab-disabled { cursor: default; }
[data-nav].ps-tab-disabled:hover { color: var(--text-2); background: transparent; }
[data-nav].ps-tab-disabled:hover > .ps-nav-dot { background: currentColor; opacity: .45; }
.ps-tab-disabled .ps-nav-dot, .ps-tab-disabled .ps-nav-label { opacity: .45; }
.ps-nav-help { flex: none; width: 16px; height: 16px; border-radius: 50%; border: 1px solid var(--border-2); color: var(--text-3); font-family: var(--font-mono); font-size: 10px; line-height: 1; display: inline-flex; align-items: center; justify-content: center; cursor: help; padding-top: 1px; box-sizing: border-box; }
.ps-title-row { display: flex; align-items: center; gap: 9px; min-width: 0; }
.ps-metric-label .ps-nav-help { margin-left: 6px; width: 14px; height: 14px; font-size: 9px; vertical-align: 2px; }
.ps-tipbox { position: fixed; z-index: 60; width: max-content; max-width: 320px; min-width: min-content; background: var(--bg); color: var(--text); border: 1px solid var(--border-2); border-radius: 8px; padding: 10px 12px; font-family: var(--font-ui); font-size: 11.5px; line-height: 1.9; pointer-events: none; }
.ps-tipbox::before { content: ""; position: absolute; left: -5px; top: 50%; width: 8px; height: 8px; transform: translateY(-50%) rotate(45deg); background: inherit; border-left: 1px solid var(--border-2); border-bottom: 1px solid var(--border-2); }
.ps-tipbox-more { margin-top: 8px; padding-top: 7px; border-top: 1px solid var(--border-2); font-family: var(--font-mono); font-size: 10.5px; color: var(--text-3); }
.ps-tipbox-below::before { left: var(--tip-caret-x, 20px); top: -5px; transform: rotate(45deg); border-bottom: 0; border-top: 1px solid var(--border-2); }
.ps-tipbox .ps-code-inline { font-size: 10.5px; white-space: nowrap; }
.ps-metric-sub .ps-code-inline { font-size: 10px; white-space: nowrap; }
.ps-sidebar-foot { display: flex; height: 62px; padding: 0 16px; border-top: 1px solid var(--border); flex-direction: column; justify-content: center; gap: 6px; }
.ps-sidebar-foot-status { display: flex; align-items: center; gap: 8px; }
.ps-sidebar-foot-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-3); flex: none; }
.ps-sidebar-foot-dot[data-ctx="ok"] { background: var(--ok-fg); }
.ps-sidebar-foot-dot[data-ctx="warn"] { background: var(--warn-fg); }
body.ps-live .ps-sidebar-foot-dot { animation: ps-pulse 2.4s ease-in-out infinite; }
.ps-sidebar-foot-text { font-family: var(--font-mono); font-size: 11px; color: var(--text-2); }
.ps-sidebar-foot-version { font-family: var(--font-mono); font-size: 11px; color: var(--text-3); }
.ps-main { display: flex; flex-direction: column; min-width: 0; }
.ps-topbar { position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 12px; row-gap: 10px; flex-wrap: wrap; padding: 18px 28px 8px; border-bottom: 0; background: color-mix(in srgb, var(--bg) 90%, transparent); backdrop-filter: blur(8px); }
.ps-topbar-title { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
.ps-topbar-eyebrow { font-family: var(--font-mono); font-size: 11px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--text-3); }
.ps-topbar-h1 { margin: 0; font-size: 18px; font-weight: 600; letter-spacing: -.3px; color: var(--text); }
.ps-gate-pass, .ps-gate-fail { display: inline-flex; align-items: center; gap: 7px; padding: 6px 11px; border-radius: 20px; font-size: 11.5px; font-family: var(--font-ui); font-weight: 700; white-space: nowrap; }
.ps-gate-pass { color: var(--ok-fg); background: var(--ok-bg); border: 0; }
.ps-gate-fail { color: var(--crit-fg); background: var(--crit-bg); border: 0; }
.ps-gate-pass::before, .ps-gate-fail::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.ps-content { padding: 24px 28px 0; overflow: auto; flex: 1; display: flex; flex-direction: column; }
.ps-panel { display: flex; flex-direction: column; gap: 14px; }
.ps-panel-narrow { max-width: 1100px; }
.ps-panel-wide { max-width: 1240px; }
[data-ghost] { cursor: pointer; transition: border-color .12s, color .12s, background .12s; border: 1px solid var(--btn-bd); border-radius: 999px; padding: 7px 11px; font-size: 12px; color: var(--btn-fg); background: var(--surface-2); font-family: var(--font-ui); font-weight: 600; }
[data-ghost]:hover { border-color: var(--brand); color: var(--brand-2); background: var(--accent-bg); }
.ps-theme-toggle { display: inline-flex; align-items: center; justify-content: center; gap: 8px; width: 96px; height: 34px; box-sizing: border-box; border: 1px solid var(--btn-bd); border-radius: 999px; padding: 0 12px; font-family: var(--font-ui); font-size: 12px; font-weight: 600; line-height: normal; color: var(--btn-fg); background: var(--surface-2); cursor: pointer; transition: border-color .25s ease, color .25s ease, background .25s ease; }
.ps-theme-toggle:hover { border-color: var(--brand); color: var(--brand-2); background: var(--accent-bg); }
.ps-theme-toggle .ps-th-svg { flex: none; color: var(--brand-text); }
.ps-theme-toggle.ps-anim .ps-th-svg { animation: ps-spin-in .44s cubic-bezier(.34,1.5,.5,1); }
.ps-theme-toggle.ps-anim .ps-th-lbl { animation: ps-fade .3s ease; }
.ps-search-box { display: flex; align-items: center; gap: 8px; border: 1px solid var(--border-2); background: var(--surface-2); border-radius: 999px; padding: 0 11px; height: 34px; flex: 0 0 390px; min-width: 0; }
.ps-search-box .ps-search-glyph { font-size: 0; flex: none; width: 14px; height: 14px; background-color: var(--text-3);
-webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.4" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.35-4.35"/></svg>') center / contain no-repeat;
mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.4" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.35-4.35"/></svg>') center / contain no-repeat; }
.ps-search-box .ps-search { display: flex; width: auto; flex: 1; min-width: 0; border: 0; background: transparent; padding: 0 0 0 1px; font-size: 12.5px; font-family: var(--font-ui); color: var(--text); outline: none; }
.ps-search-box .ps-search-kbd { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); border: 1px solid var(--border-2); border-radius: 6px; padding: 1px 5px; }
.ps-card { border: 1px solid transparent; border-radius: var(--r); background: var(--surface); overflow: hidden; }
.ps-card:has(> .ps-table) { overflow-x: auto; overflow-y: hidden; }
.ps-metrics { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.ps-metrics-3 { grid-template-columns: repeat(3, 1fr); }
.ps-metric { border: 1px solid transparent; border-radius: var(--r); background: var(--surface); padding: 18px 20px; display: flex; flex-direction: column; gap: 7px; }
.ps-metric[data-grad="crit"] { background-color: var(--crit-bg); }
.ps-metric[data-grad="warn"] { background-color: var(--warn-bg); }
.ps-metric[data-grad="ok"] { background-color: var(--ok-bg); }
.ps-metric[data-grad="info"] { background-color: var(--info-bg); }
.ps-metric[data-grad="muted"] { opacity: .55; border: 1px dashed var(--border-2); }
[data-tone="muted"] { color: var(--text-3); }
.ps-metric[data-grad="brand"] { background-color: var(--surface); }
.ps-metric [data-tone="brand"] { color: var(--text); }
.ps-metric[data-grad="accent"] { background-color: var(--accent-bg); }
#findings-metrics .ps-metric[data-kpi-link] { cursor: pointer; transition: box-shadow .12s; }
#findings-metrics .ps-metric[data-kpi-link]:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
#findings-metrics .ps-metric[data-kpi-link]:hover { box-shadow: 0 0 0 1.5px var(--btn-bd); }
#findings-metrics .ps-metric[data-kpi] .ps-metric-label { color: rgba(255,255,255,.88); }
#findings-metrics .ps-metric[data-kpi] .ps-metric-value { color: #fff; }
#findings-metrics .ps-metric[data-kpi] .ps-metric-sub { color: rgba(255,255,255,.78); }
#findings-metrics .ps-metric[data-kpi] [data-tone] { color: #fff; }
#findings-metrics .ps-metric[data-kpi="ok"] { background-color: #0ba671; }
#findings-metrics .ps-metric[data-kpi="info"] { background-color: #2e6ca3; }
#findings-metrics .ps-metric[data-kpi="warn"] { background-color: #d97c24; }
#findings-metrics .ps-metric[data-kpi="crit"] { background-color: #bf4136; }
:root[data-theme="dark"] #findings-metrics .ps-metric[data-kpi="ok"] { background-color: #0d8a60; }
:root[data-theme="dark"] #findings-metrics .ps-metric[data-kpi="info"] { background-color: #2f6494; }
:root[data-theme="dark"] #findings-metrics .ps-metric[data-kpi="warn"] { background-color: #b06a24; }
:root[data-theme="dark"] #findings-metrics .ps-metric[data-kpi="crit"] { background-color: #a8443b; }
.ps-metric-label { font-family: var(--font-ui); font-size: 13px; letter-spacing: 0; text-transform: none; font-weight: 500; color: var(--text-2); }
.ps-metric-value { font-size: 28px; font-weight: 600; letter-spacing: -.4px; word-break: break-word; }
.ps-metric-value-sm { font-size: 16px; line-height: 1.4; }
.ps-metric-sub { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); }
.ps-hero { display: grid; grid-template-columns: 300px 1fr; overflow: hidden; }
.ps-hero-left { padding: 24px 26px; border-right: 0; background: var(--accent-bg); }
.ps-hero-left[data-tone="crit"] { background: var(--crit-bg); }
.ps-hero-verdict { display: flex; align-items: center; gap: 12px; margin-top: 14px; }
.ps-hero-dot { width: 13px; height: 13px; border-radius: 50%; background: currentColor; flex: none; }
.ps-hero-big { font-size: 44px; font-weight: 700; letter-spacing: .02em; line-height: 1; }
.ps-hero-line { margin: 14px 0 0; font-size: 13px; color: var(--text-2); max-width: 230px; }
.ps-hero-right { padding: 20px 24px; display: flex; flex-direction: column; justify-content: center; gap: 16px; }
.ps-hero-rules { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 22px; }
.ps-hero-rule { display: flex; align-items: flex-start; gap: 10px; min-width: 0; }
.ps-hero-rule-chip { width: 16px; height: 16px; border-radius: 5px; display: flex; align-items: center; justify-content: center; font-size: 11px; flex: none; margin-top: 1px; }
.ps-hero-rule-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ps-hero-rule-name { font-size: 12.5px; font-weight: 500; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ps-hero-rule-detail { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); }
.ps-hero-meta { display: flex; flex-wrap: wrap; gap: 18px; padding-top: 14px; border-top: 1px solid var(--border); font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); }
.ps-hero-ingest { margin-top: 8px; font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); }
.ps-hero-ingest[data-gap="true"] { color: var(--warn-fg); }
#correlations-list { display: flex; flex-direction: column; gap: 10px; }
.ps-corr-card { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; border: 1px solid transparent; border-radius: var(--r); background: var(--surface); padding: 15px 18px; }
.ps-corr-tag { padding: 3px 8px; border-radius: 5px; font-size: 11.5px; }
.ps-corr-path { font-family: var(--font-mono); font-size: 12px; color: var(--text); }
.ps-corr-meta { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); margin-left: auto; white-space: nowrap; }
.ps-formula-bandeau { display: flex; align-items: center; gap: 22px; flex-wrap: wrap; border: 1px solid transparent; border-radius: var(--r); background: var(--accent-bg); padding: 18px 22px; }
.ps-formula-code { font-family: var(--font-mono); font-size: 17px; color: var(--text); font-weight: 500; background: var(--surface-2); border: 0; border-radius: 10px; padding: 8px 14px; }
.ps-formula-text { font-size: 12.5px; color: var(--text-2); max-width: 560px; }
.ps-diff-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
.ps-row.ps-diff-row { flex-direction: row; align-items: center; gap: 10px; padding: 12px 18px; }
.ps-diff-type { font-size: 12.5px; color: var(--text); }
.ps-diff-endpoint { font-family: var(--font-mono); font-size: 12px; color: var(--text-3); margin-left: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ps-card .ps-table th:first-child, .ps-card .ps-table td:first-child { padding-left: 18px; }
.ps-card .ps-table th:last-child, .ps-card .ps-table td:last-child { padding-right: 18px; }
.ps-overview-cols { display: grid; grid-template-columns: 1.55fr 1fr; gap: 14px; align-items: start; }
.ps-overview-card-head { display: flex; align-items: center; justify-content: space-between; padding: 16px 18px 4px; border-bottom: 0; }
.ps-overview-card-title { font-size: 14px; font-weight: 600; color: var(--text); }
.ps-overview-link { font-family: var(--font-ui); font-weight: 600; font-size: 11.5px; color: var(--btn-fg); background: var(--surface-2); border: 1px solid var(--btn-bd); border-radius: 999px; padding: 5px 11px; cursor: pointer; }
.ps-overview-link:hover { color: var(--brand-2); background: var(--accent-bg); border-color: var(--brand); }
.ps-overview-rail { display: flex; flex-direction: column; gap: 14px; }
.ps-row.ps-overview-finding { flex-direction: row; align-items: center; justify-content: space-between; gap: 12px; }
.ps-of-left { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.ps-of-right { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex: none; }
.ps-of-primary { font-family: var(--font-mono); font-size: 12px; color: var(--text-2); }
.ps-of-secondary { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); }
.ps-mini-card { border: 1px solid transparent; border-radius: var(--r); background: var(--surface); padding: 16px 18px; display: flex; flex-direction: column; gap: 13px; }
.ps-mini-head { display: flex; align-items: baseline; justify-content: space-between; }
.ps-mini-title { font-size: 14px; font-weight: 600; color: var(--text); }
.ps-mini-total { font-family: var(--font-mono); font-size: 12px; color: var(--brand-text); }
.ps-mini-pills { display: flex; gap: 9px; flex-wrap: wrap; }
.ps-mini-pill { padding: 4px 10px; border-radius: 20px; font-size: 11.5px; }
.ps-mini-link { font-family: var(--font-ui); font-weight: 600; font-size: 11.5px; color: var(--btn-fg); background: var(--surface-2); border: 1px solid var(--btn-bd); border-radius: 999px; padding: 5px 11px; cursor: pointer; align-self: flex-start; }
.ps-mini-link:hover { color: var(--brand-2); background: var(--accent-bg); border-color: var(--brand); }
.ps-link-txt { text-decoration: underline; }
.ps-mini-row { display: flex; flex-direction: column; gap: 5px; }
.ps-mini-row-head { display: flex; justify-content: space-between; font-family: var(--font-mono); font-size: 11.5px; }
.ps-bar-track { height: 6px; border-radius: 4px; background: var(--surface-3); overflow: hidden; }
.ps-bar-fill { height: 100%; border-radius: 4px; background: var(--brand); }
.ps-filters { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.ps-chip-group { display: contents; }
.ps-chip { padding: 5px 13px; font-size: 11.5px; border-radius: 20px; border: 1px solid var(--border-2); background: var(--surface-2); cursor: pointer; color: var(--text-2); font-family: var(--font-ui); font-weight: 500; }
.ps-chip:hover { background: var(--accent-bg); border-color: var(--btn-bd); }
.ps-chip.active, .ps-chip.ps-sort-on { background: var(--brand); color: #fff; border-color: var(--brand); }
.ps-chip[data-sev] { font-weight: 600; }
.ps-chip[data-sev="crit"] { color: var(--crit-fg); background: var(--crit-bg); border-color: transparent; }
.ps-chip[data-sev="warn"] { color: var(--warn-fg); background: var(--warn-bg); border-color: transparent; }
.ps-chip[data-sev="info"] { color: var(--info-fg); background: var(--info-bg); border-color: transparent; }
.ps-chip[data-sev].active { box-shadow: inset 0 0 0 1px currentColor; }
.ps-findings-count { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); margin-left: 6px; }
.ps-sort-group { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; }
.ps-sort-label { font-family: var(--font-mono); font-size: 11px; color: var(--text-3); }
.ps-list { display: flex; flex-direction: column; padding: 10px; gap: 8px; }
.ps-row { display: flex; flex-direction: column; gap: 7px; padding: 13px 16px; background: var(--surface-2); border-radius: 10px; cursor: pointer; transition: background .12s, box-shadow .12s; }
.ps-row:hover { background: var(--surface-2); box-shadow: 0 0 0 1.5px var(--btn-bd); }
.ps-row.selected { background: var(--surface-2); box-shadow: inset 0 0 0 1.5px var(--brand); }
.ps-sev { padding: 3px 9px; border-radius: 8px; font-size: 11.5px; }
.ps-badge-estimated, .ps-badge-measured { display: inline-block; padding: 2px 7px; border-radius: 8px; font-size: 11.5px; font-family: var(--font-ui); font-weight: 600; }
.ps-badge-estimated { color: var(--warn-fg); background: var(--warn-bg); border: 0; }
.ps-badge-measured { color: var(--ok-fg); background: var(--ok-bg); border: 0; }
.ps-scoring-bandeau { display: flex; align-items: center; gap: 8px; font-size: 12px; flex-wrap: wrap; }
.ps-scoring-label { color: var(--text-2); font-weight: 500; }
.ps-scoring-chip { display: inline-block; padding: 3px 10px; border-radius: 20px; font-size: 11.5px; font-weight: 500; white-space: nowrap; font-family: var(--font-mono); }
.ps-scoring-chip-neutral { background: var(--surface-2); color: var(--text-2); }
.ps-scoring-chip-warning { background: var(--warn-bg); color: var(--warn-fg); }
.ps-scoring-chip-accent { background: var(--accent-bg); color: var(--brand-text); }
.ps-fin-main { min-width: 0; }
.ps-fin-type { font-weight: 600; font-size: 12.5px; color: var(--text); }
.ps-fin-service { color: var(--text-3); font-weight: 400; font-size: 12px; font-family: var(--font-mono); }
.ps-fin-detail { font-size: 12px; color: var(--text-2); font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ps-fin-right { font-size: 12px; color: var(--text-2); font-family: var(--font-mono); text-align: right; white-space: nowrap; }
.ps-fin-right-sub { color: var(--text-3); font-size: 11.5px; }
.ps-fin-topline { display: flex; align-items: center; gap: 8px; }
.ps-fin-metric { margin-left: auto; }
.ps-fin-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ps-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.ps-detail-titlewrap { flex: 1; min-width: 0; }
.ps-triage { display: grid; grid-template-columns: 340px 1fr; gap: 14px; align-items: start; }
.ps-detail { min-width: 0; border: 1px solid transparent; border-radius: var(--r); background: var(--surface); padding: 20px 22px; display: flex; flex-direction: column; gap: 17px; }
#explain-detail-head, #explain-content { display: flex; flex-direction: column; gap: 18px; }
.ps-detail-empty { color: var(--text-3); font-size: 13px; }
.ps-copy-trace { margin-left: 10px; padding: 2px 9px; font-size: 10.5px; vertical-align: middle; }
.ps-detail-head { display: flex; align-items: flex-start; gap: 12px; }
.ps-detail-h2 { margin: 0; font-size: 18px; font-weight: 600; letter-spacing: -.3px; color: var(--text); }
.ps-detail-sub { margin: 4px 0 0; font-size: 12.5px; color: var(--text-2); }
.ps-detail-suggestion { margin: 0; font-size: 12.5px; line-height: 1.55; color: var(--text); }
.ps-meta-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.ps-meta-cell { min-width: 0; border: 0; border-radius: 10px; background: var(--surface-2); padding: 11px 12px; }
.ps-meta-cell-label { font-family: var(--font-mono); font-size: 11px; letter-spacing: .6px; text-transform: uppercase; color: var(--text-3); }
.ps-meta-cell-value { font-size: 12.5px; font-weight: 500; color: var(--text); margin-top: 5px; overflow-wrap: anywhere; }
.ps-meta-cell-value.mono { font-family: var(--font-mono); font-size: 11.5px; font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ps-meta-cell-value.brand { color: var(--brand-text); }
.ps-eyebrow { font-family: var(--font-mono); font-size: 11px; letter-spacing: 1.1px; text-transform: uppercase; color: var(--text-3); }
.ps-eyebrow.brand { color: var(--brand-text); }
.ps-codeblock { display: block; font-family: var(--font-mono); font-size: 12px; color: var(--text); background: var(--surface-2); border: 0; border-radius: 10px; padding: 14px 16px; overflow: auto; white-space: pre-wrap; }
.ps-section { display: flex; flex-direction: column; gap: 8px; }
.ps-tree-header { font-size: 11.5px; color: var(--text-2); padding: 10px 12px; background: var(--surface-2); border: 0; border-radius: 10px; font-family: var(--font-mono); word-break: break-all; }
.ps-tree { border: 0; border-radius: 12px; background: var(--surface-2); padding: 10px; display: flex; flex-direction: column; gap: 2px; }
.ps-span { display: flex; align-items: center; gap: 9px; padding: 5px 10px; border-radius: 8px; }
.ps-span.dim { color: var(--text-3); }
.ps-span.hilite { background: var(--crit-bg); }
.ps-span.hilite .ps-span-text { color: var(--crit-fg); font-weight: 700; }
.ps-span.hilite .ps-span-dur { color: var(--crit-fg); }
.ps-span.hilite .ps-span-count { background: var(--surface-2); color: var(--crit-fg); }
[data-fsev="warning"] .ps-span.hilite { background: var(--warn-bg); }
[data-fsev="warning"] .ps-span.hilite .ps-span-text,
[data-fsev="warning"] .ps-span.hilite .ps-span-dur,
[data-fsev="warning"] .ps-span.hilite .ps-span-count { color: var(--warn-fg); }
[data-fsev="info"] .ps-span.hilite { background: var(--info-bg); }
[data-fsev="info"] .ps-span.hilite .ps-span-text,
[data-fsev="info"] .ps-span.hilite .ps-span-dur,
[data-fsev="info"] .ps-span.hilite .ps-span-count { color: var(--info-fg); }
.ps-span-text { font-family: var(--font-mono); font-size: 12px; color: var(--text); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ps-span-flag { padding: 1px 6px; border-radius: 4px; font-size: 11.5px; }
.ps-span-dur { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); margin-left: auto; white-space: nowrap; }
.ps-span-count { flex: none; font-family: var(--font-mono); font-size: 11.5px; padding: 0 6px; border-radius: 5px; background: var(--surface-3); color: var(--text-2); }
.ps-span-find { color: var(--crit-fg); font-weight: 600; }
.ps-span.ps-span-pgstat-link { cursor: pointer; }
.ps-span.ps-span-pgstat-link:hover .ps-span-text { text-decoration: underline; }
.ps-fix { border: 0; border-radius: 12px; background: var(--fix-bg); padding: 14px 16px; display: flex; flex-direction: column; gap: 9px; }
.ps-fix .ps-eyebrow { color: var(--fix-fg); }
.ps-detail-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start; }
.ps-detail-cols.single { grid-template-columns: 1fr; }
.ps-detail-foot { font-family: var(--font-mono); font-size: 11px; color: var(--text-3); border-top: 1px solid var(--border); padding-top: 13px; overflow-wrap: anywhere; }
.ps-pgstat-xref { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 9px 12px; background: var(--surface-2); border: 0; border-radius: 10px; font-family: var(--font-mono); font-size: 12px; }
.ps-pgstat-xref-stat { color: var(--text-3); white-space: nowrap; }
.ps-fix-prose { font-size: 12.5px; color: var(--text); line-height: 1.55; background: var(--surface-2); border-radius: 8px; padding: 10px 13px; }
.ps-fix-code { font-family: var(--font-mono); font-size: 12px; color: var(--text); background: var(--surface-2); border: 0; border-radius: 6px; padding: 9px 11px; overflow: auto; white-space: pre; }
.ps-fix-alt { display: block; font-size: 12px; color: var(--text); line-height: 1.55; padding-top: 9px; border-top: 1px solid color-mix(in srgb, var(--fix-fg) 25%, transparent); }
.ps-fix-link { align-self: flex-start; font-family: var(--font-mono); font-size: 11px; color: var(--fix-fg); }
.ps-source-link { color: var(--brand-text); text-decoration: underline; }
.ps-code-inline { font-family: var(--font-mono); font-size: 11.5px; background: var(--surface-2); border: 1px solid var(--border-2); border-radius: 5px; padding: 1px 6px; color: var(--text); }
.ps-hl-kw { font-weight: 600; }
.ps-banner { background: var(--info-bg); border: 0; border-radius: 10px; padding: 8px 12px; font-size: 11.5px; color: var(--info-fg); }
.ps-empty { padding: 30px; text-align: center; color: var(--text-2); font-size: 13px; background: var(--surface); border: 1px solid transparent; border-radius: var(--r); }
.ps-drill { background: var(--info-bg); border: 0; border-radius: 10px; padding: 10px 12px; font-size: 11.5px; color: var(--info-fg); }
.ps-drill-label { font-size: 11.5px; color: var(--info-fg); margin-bottom: 4px; opacity: .8; }
.ps-drill-link { color: var(--info-fg); }
.ps-drill-clear { color: var(--info-fg); text-decoration: underline; cursor: pointer; margin-left: 8px; }
.ps-table { width: 100%; font-size: 12px; font-family: var(--font-mono); border-collapse: collapse; }
.ps-table th { text-align: left; font-weight: 600; padding: 11px 12px; border-bottom: 1px solid var(--border); color: var(--text-3); font-size: 11px; letter-spacing: .2px; text-transform: none; font-family: var(--font-ui); }
.ps-table th[data-sortable] { cursor: pointer; user-select: none; white-space: nowrap; transition: color .12s; }
.ps-table th[data-sortable]:hover { color: var(--text); }
.ps-table th .ps-sort-ico { display: inline-block; margin-left: 5px; opacity: .4; font-family: var(--font-mono); font-size: 10px; }
.ps-table th[aria-sort] { color: var(--brand-text); }
.ps-table th[aria-sort] .ps-sort-ico { opacity: 1; color: var(--brand-text); }
.ps-table td { padding: 12px; border-bottom: 1px solid var(--border); vertical-align: top; color: var(--text-2); }
.ps-table td[data-tone="crit"] { color: var(--crit-fg); }
.ps-table td[data-tone="ok"] { color: var(--ok-fg); }
.ps-table td.ps-diff-after { color: var(--text); font-weight: 600; }
.ps-table tr:last-child td { border-bottom-color: transparent; }
#diff-sev-table th:nth-child(3), #diff-sev-table th:nth-child(4),
#diff-sev-table td:nth-child(3), #diff-sev-table td:nth-child(4) { text-align: center; }
#pgstat-body tr.hilite { background: var(--crit-bg); }
#pgstat-body tr.hilite td:first-child { color: var(--crit-fg); font-weight: 600; }
.ps-pgstat-controls { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ps-rankby-label { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); margin-right: 2px; }
.ps-pgstat-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.ps-footer { --footer-inset: 28px; position: sticky; bottom: 0; z-index: 5; background: color-mix(in srgb, var(--bg) 92%, transparent); backdrop-filter: blur(8px); margin-top: auto; min-height: 62px; padding: 0 var(--footer-inset); display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); }
.ps-footer::before { content: ""; position: absolute; top: 0; left: var(--footer-inset); right: var(--footer-inset); height: 1px; background: var(--border); }
:root[data-density="compact"] .ps-footer { --footer-inset: 22px; }
.ps-credit { margin-left: auto; }
.ps-credit a, .ps-footer a { color: var(--brand-text); text-decoration: underline; text-decoration-thickness: .5px; text-decoration-color: color-mix(in srgb, var(--brand-text) 55%, transparent); }
.ps-credit a:hover, .ps-footer a:hover { color: var(--text); }
.ps-kbd { font-family: var(--font-mono); font-size: 11.5px; border: 1px solid var(--border-2); border-radius: 6px; padding: 1px 6px; color: var(--text-3); }
.ps-diff-section { display: flex; flex-direction: column; }
.ps-diff-section-header { font-size: 13px; font-weight: 600; padding: 15px 18px 4px; border-bottom: 0; color: var(--text); }
.ps-diff-section-header.empty { padding-bottom: 15px; }
.ps-diff-section-header.red { color: var(--crit-fg); }
.ps-diff-section-header.green { color: var(--ok-fg); }
.ps-diff-section-new[data-sev="critical"] { background-color: var(--crit-bg); }
.ps-diff-section-new[data-sev="warning"] { background-color: var(--warn-bg); }
.ps-diff-section-new[data-sev="info"] { background-color: var(--info-bg); }
.ps-diff-section-new[data-sev="critical"] .ps-diff-section-header { color: var(--crit-fg); }
.ps-diff-section-new[data-sev="warning"] .ps-diff-section-header { color: var(--warn-fg); }
.ps-diff-section-new[data-sev="info"] .ps-diff-section-header { color: var(--info-fg); }
.ps-diff-section-resolved { background-color: var(--ok-bg); }
:root[data-theme="dark"] .ps-diff-section-new[data-sev="critical"] { background-color: color-mix(in srgb, var(--crit-bg) 72%, #000); }
:root[data-theme="dark"] .ps-diff-section-new[data-sev="warning"] { background-color: color-mix(in srgb, var(--warn-bg) 72%, #000); }
:root[data-theme="dark"] .ps-diff-section-new[data-sev="info"] { background-color: color-mix(in srgb, var(--info-bg) 72%, #000); }
:root[data-theme="dark"] .ps-diff-section-resolved { background-color: color-mix(in srgb, var(--ok-bg) 72%, #000); }
.ps-quality-gate { display: flex; flex-direction: column; gap: 8px; padding: 10px; }
.ps-section-title { font-size: 14px; font-weight: 600; margin: 0; color: var(--text); }
.ps-panel-toolbar { display: flex; justify-content: flex-end; align-items: center; gap: 8px; }
.ps-export-btn, .ps-copy-link-btn { display: inline-flex; align-items: center; gap: 6px; padding: 7px 14px; font-size: 11.5px; border-radius: 999px; border: 1px solid var(--btn-bd); background: var(--surface-2); cursor: pointer; color: var(--btn-fg); font-family: var(--font-ui); font-weight: 600; }
.ps-export-btn:hover, .ps-copy-link-btn:hover { border-color: var(--brand); color: var(--brand-2); background: var(--accent-bg); }
.ps-export-btn::before { content: ""; flex: none; width: 12px; height: 12px; background-color: currentColor;
-webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8M8 17h8M12 13v4"/></svg>') center / contain no-repeat;
mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M8 13h8M8 17h8M12 13v4"/></svg>') center / contain no-repeat; }
.ps-copy-link-btn::before { content: ""; flex: none; width: 12px; height: 12px; background-color: currentColor;
-webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>') center / contain no-repeat;
mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>') center / contain no-repeat; }
.ps-copy-link-btn.ps-copied::before {
-webkit-mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>') center / contain no-repeat;
mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>') center / contain no-repeat; }
.ps-corr-side { display: inline-flex; align-items: center; gap: 10px; padding: 5px 9px; margin: -5px -9px; border-radius: 9px; }
.ps-corr-side-link { cursor: pointer; transition: background .12s, box-shadow .12s; }
.ps-corr-side-link:hover { background: var(--accent-bg); box-shadow: 0 0 0 1.5px var(--btn-bd); }
.ps-show-more { display: block; padding: 12px 14px; font-size: 12px; border: 0; border-radius: 8px; margin: 0 10px 10px; background: var(--surface-2); color: var(--brand-text); cursor: pointer; font-family: var(--font-mono); width: calc(100% - 20px); text-align: center; transition: box-shadow .12s; }
.ps-show-more:hover { background: var(--surface-2); box-shadow: 0 0 0 1.5px var(--btn-bd); }
@keyframes ps-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
@keyframes ps-spin-in { from { transform: rotate(-120deg) scale(.3); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes ps-fade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: none; } }
@keyframes ps-refresh-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
dialog.ps-modal { position: fixed; inset: 0; margin: auto; max-width: 460px; width: calc(100% - 40px); height: fit-content; max-height: calc(100vh - 40px); overflow-y: auto; background: var(--surface); color: var(--text); border-radius: 16px; border: 0; padding: 22px 24px; box-shadow: 0 24px 70px rgba(0,0,0,.25); }
dialog.ps-modal::backdrop { background: rgba(0,0,0,.55); }
dialog.ps-modal-wide { max-width: 560px; }
#topic-help-body h3 { margin: 14px 0 4px; font-size: 12px; font-weight: 600; letter-spacing: .6px; text-transform: uppercase; color: var(--text-3); font-family: var(--font-mono); }
#topic-help-body p { margin: 0; font-size: 12.5px; line-height: 1.75; color: var(--text); }
#topic-help-link { display: inline-block; margin-top: 16px; }
dialog.ps-modal h2 { margin: 0 0 16px 0; font-size: 15px; font-weight: 600; }
.ps-modal-close { position: absolute; top: 14px; right: 16px; background: transparent; border: none; color: var(--text-2); font-family: var(--font-mono); font-size: 12px; cursor: pointer; line-height: 1; padding: 4px 8px; }
.ps-modal-close:hover { color: var(--text); }
dialog.ps-modal table { width: 100%; border-collapse: collapse; }
dialog.ps-modal th { text-align: left; padding: 4px 6px; font-size: 11.5px; font-weight: 600; color: var(--text-3); border-bottom: 1px solid var(--border); font-family: var(--font-mono); }
dialog.ps-modal th:first-child { width: 110px; }
dialog.ps-modal td { padding: 6px 6px; font-size: 12.5px; vertical-align: top; color: var(--text-2); }
dialog.ps-modal td:first-child { width: 110px; white-space: nowrap; font-family: var(--font-mono); color: var(--text); }
dialog.ps-modal label { display: block; font-size: 11.5px; color: var(--text-3); margin: 12px 0 4px 0; font-family: var(--font-ui); font-weight: 600; text-transform: none; letter-spacing: .2px; }
dialog.ps-modal input[type="password"], dialog.ps-modal input[type="text"], dialog.ps-modal textarea, dialog.ps-modal select { width: 100%; box-sizing: border-box; padding: 8px 11px; font-size: 12.5px; font-family: inherit; background: var(--surface-3); border: 0; border-radius: 8px; color: var(--text); }
dialog.ps-modal textarea { min-height: 70px; resize: vertical; }
dialog.ps-modal .ps-modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 16px; }
dialog.ps-modal .ps-modal-btn { padding: 8px 15px; font-size: 12.5px; border-radius: 999px; border: 1px solid var(--border); background: transparent; color: var(--text-2); cursor: pointer; font-family: inherit; }
dialog.ps-modal .ps-modal-btn:hover { border-color: var(--border-2); color: var(--text); }
dialog.ps-modal .ps-modal-btn.primary { background: var(--brand); color: #fff; border: 0; font-weight: 600; }
dialog.ps-modal .ps-modal-error { display: none; margin-top: 10px; padding: 8px 10px; font-size: 11px; background: var(--crit-bg); color: var(--crit-fg); border-radius: var(--r-sm); }
.ps-daemon-status { display: none; align-items: center; gap: 6px; font-size: 11.5px; color: var(--text-2); }
body.ps-live .ps-daemon-status { display: inline-flex; }
.ps-daemon-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-3); display: inline-block; }
.ps-daemon-dot.connected { background: var(--brand); }
.ps-daemon-dot.disconnected { background: var(--crit-fg); }
.ps-daemon-dot.unauthorized { background: #d98324; }
.ps-refresh-btn { display: none; padding: 0 14px; height: 34px; box-sizing: border-box; font-size: 12px; border-radius: 999px; border: 1px solid var(--btn-bd); background: var(--surface-2); color: var(--btn-fg); cursor: pointer; font-family: var(--font-ui); font-weight: 600; }
body.ps-live .ps-refresh-btn { display: inline-flex; align-items: center; gap: 6px; }
body.ps-live .ps-refresh-btn[hidden] { display: none; }
.ps-refresh-btn:hover { border-color: var(--brand); color: var(--brand-2); background: var(--accent-bg); }
.ps-refresh-btn:disabled { cursor: not-allowed; opacity: 0.6; }
.ps-refresh-ico { flex: none; }
#ps-refresh-btn.ps-spinning .ps-refresh-ico { animation: ps-refresh-spin .6s cubic-bezier(.45,.05,.35,1); }
.ps-fin-actions { display: none; margin-left: 8px; align-self: flex-start; }
body.ps-live .ps-fin-actions { display: inline-flex; gap: 6px; }
.ps-fin-action-btn { padding: 5px 11px; font-size: 12px; border-radius: 999px; border: 1px solid var(--btn-bd); background: var(--surface-2); color: var(--ok-fg); cursor: pointer; font-family: inherit; }
.ps-fin-action-btn:hover { background: var(--ok-bg); }
.ps-fin-action-btn.revoke { border-color: var(--border-2); color: var(--text-2); }
.ps-fin-action-btn.revoke:hover { background: var(--surface-3); }
.ps-fin-action-btn:disabled { cursor: not-allowed; opacity: 0.5; }
.ps-include-acked-wrap { display: none; align-items: center; gap: 6px; font-size: 11.5px; color: var(--text-2); margin-left: auto; cursor: pointer; }
.ps-include-acked-wrap input { margin: 0; width: 13px; height: 13px; }
.ps-include-acked-wrap span { height: 13px; line-height: 13px; position: relative; top: 1px; }
body.ps-live .ps-include-acked-wrap { display: inline-flex; }
.ps-acks-footer { margin-top: 10px; font-size: 11.5px; color: var(--text-3); }
.ps-toast { position: fixed; bottom: 20px; right: 20px; padding: 10px 16px; font-size: 12px; background: var(--surface); color: var(--text); border: 0; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.18); z-index: 9999; max-width: 380px; }
.ps-toast.error { background: var(--crit-bg); color: var(--crit-fg); }
.ps-toast.success { background: var(--ok-bg); color: var(--ok-fg); }
.ps-toast-undo { margin-left: 10px; border: 1px solid var(--ok-bd); background: var(--surface-2); color: var(--ok-fg); border-radius: 999px; padding: 3px 11px; font: 600 11.5px var(--font-ui); cursor: pointer; }
.ps-toast-undo:hover { background: var(--ok-bg); }
mark.ps-mark { background: color-mix(in srgb, var(--brand) 24%, transparent); color: inherit; border-radius: 3px; padding: 0 1px; }
:root[data-density="compact"] { --r: 13px; --r-sm: 7px; }
:root[data-density="compact"] body { font-size: 12.5px; }
:root[data-density="compact"] .ps-content { padding: 16px 22px 0; }
:root[data-density="compact"] .ps-topbar { padding: 12px 22px 6px; }
:root[data-density="compact"] .ps-topbar-h1 { font-size: 16px; }
:root[data-density="compact"] .ps-nav { padding: 6px 10px; gap: 2px; }
:root[data-density="compact"] [data-nav] { padding: 7px 10px; font-size: 12.5px; border-radius: 8px; }
:root[data-density="compact"] .ps-metric { padding: 12px 14px; gap: 5px; }
:root[data-density="compact"] .ps-metric-value { font-size: 22px; }
:root[data-density="compact"] .ps-list, :root[data-density="compact"] .ps-quality-gate { padding: 7px; gap: 6px; }
:root[data-density="compact"] .ps-row { padding: 9px 12px; border-radius: 8px; }
:root[data-density="compact"] .ps-show-more { margin: 0 7px 7px; width: calc(100% - 14px); }
:root[data-density="compact"] .ps-detail { padding: 15px 16px; gap: 13px; }
:root[data-density="compact"] .ps-panel { gap: 10px; }
:root[data-density="compact"] .ps-metrics { gap: 8px; }
:root[data-density="compact"] .ps-brand-block { padding: 14px 14px 8px; }
:root[data-density="compact"] .ps-logo svg, :root[data-density="compact"] .ps-logo img { height: 28px; }
:root[data-density="compact"] .ps-hero-big { font-size: 36px; }
:root[data-density="compact"] .ps-hero-left { padding: 18px 20px; }
:root[data-density="compact"] .ps-hero-right { padding: 14px 18px; }
:root[data-density="compact"] .ps-table th { padding: 8px 10px; }
:root[data-density="compact"] .ps-table td { padding: 8px 10px; }
#density-toggle { width: 103px; padding: 0 14px; gap: 6px; }
#density-toggle svg { flex: none; color: var(--brand-text); }
#density-toggle .b1, #density-toggle .b3 { transition: transform .28s cubic-bezier(.34,1.4,.5,1); }
:root[data-density="compact"] #density-toggle .b1 { transform: translateY(2.4px); }
:root[data-density="compact"] #density-toggle .b3 { transform: translateY(-2.4px); }
:root[data-density="compact"] #density-toggle:hover .b1,
:root[data-density="compact"] #density-toggle:hover .b3 { transform: translateY(0); }
:root[data-density="comfort"] #density-toggle:hover .b1 { transform: translateY(2.4px); }
:root[data-density="comfort"] #density-toggle:hover .b3 { transform: translateY(-2.4px); }
button:focus-visible, [data-nav]:focus-visible, .ps-chip:focus-visible, a:focus-visible,
.ps-row:focus-visible, .ps-table th[data-sortable]:focus-visible,
.ps-nav-help:focus-visible {
outline: 2px solid var(--brand); outline-offset: 2px; }
input.ps-search:focus-visible { outline: none; }
.ps-search-box:focus-within { border-color: var(--brand); }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}
@media (max-width: 1180px) {
.ps-theme-toggle { width: 40px; padding: 0; gap: 0; justify-content: center; }
.ps-theme-toggle .ps-th-lbl { display: none; }
#density-toggle { width: 40px; padding: 0; }
#density-label { display: none; }
body.ps-live #ps-refresh-btn { width: 34px; padding: 0; justify-content: center; gap: 0; }
#ps-refresh-btn span { display: none; }
.ps-search-box { flex: 1 1 180px; min-width: 140px; }
}
@media (max-width: 920px) {
.ps-shell { grid-template-columns: 200px 1fr; }
.ps-hero { grid-template-columns: 1fr; }
.ps-hero-left { border-right: 0; border-bottom: 1px solid var(--border); }
.ps-hero-rules { grid-template-columns: 1fr; }
.ps-metrics { grid-template-columns: repeat(2, 1fr); }
.ps-overview-cols, .ps-diff-cols, .ps-triage { grid-template-columns: 1fr; }
.ps-meta-grid { grid-template-columns: repeat(2, 1fr); }
.ps-search-box { flex: 1 1 auto; min-width: 0; }
.ps-footer { --footer-inset: 22px; min-height: 0; padding-top: 18px; padding-bottom: 14px; }
}
@media (max-height: 700px) {
.ps-footer { position: static; background: transparent; backdrop-filter: none; }
}
</style>
</head>
<body>
<div id="ps-no-js" style="padding:24px;max-width:60em;font:16px/1.6 system-ui,sans-serif">
<strong>This report did not build.</strong>
Its content is rendered by the JavaScript embedded in this file, and the page
loaded without running it. Either the browser has JavaScript turned off, or
the server sent a Content-Security-Policy that blocks it, which a Jenkins
controller does by default while GitHub Pages and GitLab Pages do not.
Remedies per CI provider:
<a href="https://perf-sentinel.dev/docs/CI" target="_blank" rel="noopener noreferrer">perf-sentinel.dev/docs/CI</a>.
Opening this file straight from disk always works.
</div>
<script>var n=document.getElementById("ps-no-js");if(n){n.remove()}</script>
<div class="ps-shell" id="ps-shell">
<aside class="ps-sidebar">
<div class="ps-sidebar-sticky">
<div class="ps-brand-block">
<a class="ps-brand" href="https://perf-sentinel.dev/" target="_blank" rel="noopener noreferrer" aria-label="perf-sentinel, perf-sentinel.dev">{{BRAND_LOGO}}</a>
<span class="ps-brand-eyebrow">HTML REPORT</span>
</div>
<div class="ps-nav" id="tabs" role="tablist" aria-label="Dashboard sections" aria-orientation="vertical">
<div class="ps-nav-eyebrow">// sections</div>
</div>
<div class="ps-sidebar-foot">
<div class="ps-sidebar-foot-status">
<span class="ps-sidebar-foot-dot" id="ps-sidebar-dot"></span>
<span class="ps-sidebar-foot-text" id="ps-sidebar-context"></span>
</div>
<div class="ps-sidebar-foot-version" id="ps-sidebar-version"></div>
</div>
</div>
</aside>
<main class="ps-main">
<header class="ps-topbar">
<div class="ps-topbar-title">
<span class="ps-topbar-eyebrow" id="topbar-eyebrow"></span>
<div class="ps-title-row"><h1 class="ps-topbar-h1" id="topbar-h1">Overview</h1></div>
</div>
<div class="ps-topbar-actions" style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end;">
<div class="ps-search-box" id="topbar-search-box">
<span class="ps-search-glyph">⌕</span>
<input type="search" class="ps-search" id="topbar-search" placeholder="Search findings, templates, endpoints…" aria-label="Search findings, templates, endpoints" />
<span class="ps-search-kbd">⌘K</span>
</div>
<span id="topbar-gate"></span>
<output class="ps-daemon-status" id="ps-daemon-status" aria-live="polite">
<span class="ps-daemon-dot" id="ps-daemon-dot"></span>
<span id="ps-daemon-status-text">Connecting...</span>
</output>
<button class="ps-refresh-btn" id="ps-refresh-btn" type="button" aria-label="Refresh daemon data"><svg class="ps-refresh-ico" width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M13.65 5.35A6 6 0 1 0 14 8" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" fill="none"></path><path d="M14 2v3.5h-3.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" fill="none"></path></svg><span>Refresh</span></button>
<button class="ps-refresh-btn" id="ps-logout-btn" type="button" aria-label="Forget key for this tab (clears the daemon API key from session storage)" hidden>Forget key</button>
<button class="ps-theme-toggle" id="density-toggle" type="button" aria-label="Toggle UI density"><svg width="13" height="13" viewBox="0 0 16 16" aria-hidden="true"><rect class="b1" x="2" y="2.4" width="12" height="1.9" rx="0.95" fill="currentColor"></rect><rect class="b2" x="2" y="7.05" width="12" height="1.9" rx="0.95" fill="currentColor"></rect><rect class="b3" x="2" y="11.7" width="12" height="1.9" rx="0.95" fill="currentColor"></rect></svg><span id="density-label">Comfort</span></button>
<button class="ps-theme-toggle" id="theme-toggle" type="button">System</button>
</div>
</header>
<div class="ps-content">
<div id="panel-overview" role="tabpanel" aria-labelledby="tab-overview" class="ps-panel ps-panel-wide" style="display: none;">
<div id="overview-hero"></div>
<div class="ps-metrics" id="findings-metrics"></div>
<div id="overview-body"></div>
</div>
<div id="panel-findings" role="tabpanel" aria-labelledby="tab-findings" class="ps-panel">
<div class="ps-banner" id="trim-banner" style="display: none;"></div>
<div class="ps-filters" id="findings-filters"></div>
<div class="ps-panel-toolbar">
<label class="ps-include-acked-wrap" id="findings-include-acked-wrap">
<input type="checkbox" id="findings-include-acked" />
<span>Show acknowledged</span>
</label>
<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-triage">
<div class="ps-card">
<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 class="ps-detail" id="findings-detail">
<div class="ps-detail-empty" id="explain-empty">Select a finding to view its trace tree and suggested fix.</div>
<div id="explain-detail-head"></div>
<div id="explain-content" style="display: none;">
<div class="ps-tree-header" id="explain-breadcrumb"></div>
<div class="ps-section">
<span class="ps-eyebrow">// explain · trace tree</span>
<div class="ps-tree" id="explain-tree"></div>
<div class="ps-detail-empty" id="explain-not-embedded" style="display: none;"></div>
</div>
<div class="ps-detail-cols" id="explain-cols">
<div class="ps-section" id="explain-pgstat" style="display: none;"></div>
<div class="ps-fix" id="explain-fix" style="display: none;"></div>
</div>
<div class="ps-detail-foot" id="explain-foot" style="display: none;"></div>
</div>
</div>
</div>
</div>
<div id="panel-pgstat" role="tabpanel" aria-labelledby="tab-pgstat" class="ps-panel ps-panel-narrow" 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-pgstat-controls">
<span class="ps-rankby-label" id="pgstat-rankby-label">Rank by</span>
<div class="ps-filters" id="pgstat-rankings"></div>
<div class="ps-pgstat-actions">
<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>
</div>
<div class="ps-card">
<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>
<div class="ps-empty" id="pgstat-empty" style="display: none;">No pg_stat entries.</div>
</div>
<div id="panel-mysqlstat" role="tabpanel" aria-labelledby="tab-mysqlstat" class="ps-panel ps-panel-narrow" style="display: none;">
<div class="ps-pgstat-controls">
<span class="ps-rankby-label" id="mysqlstat-rankby-label">Rank by</span>
<div class="ps-filters" id="mysqlstat-rankings"></div>
<div class="ps-pgstat-actions">
<button type="button" class="ps-export-btn" id="mysqlstat-export" data-export-tab="mysqlstat">Export CSV</button>
<button type="button" class="ps-copy-link-btn" id="mysqlstat-copy-link" data-copy-link-tab="mysqlstat">Copy link</button>
</div>
</div>
<div class="ps-card">
<table class="ps-table" id="mysqlstat-table">
<thead><tr><th>Template</th><th style="width: 90px;">Schema</th><th style="width: 80px;">Calls</th><th style="width: 100px;">Total ms</th><th style="width: 90px;">Mean ms</th><th style="width: 110px;">Rows examined</th></tr></thead>
<tbody id="mysqlstat-body"></tbody>
</table>
</div>
<div class="ps-empty" id="mysqlstat-empty" style="display: none;">No mysql_stat entries.</div>
</div>
<div id="panel-diff" role="tabpanel" aria-labelledby="tab-diff" class="ps-panel ps-panel-narrow" style="display: none;">
<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-cols">
<div class="ps-card ps-diff-section ps-diff-section-new">
<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 class="ps-empty" id="diff-new-empty" style="display: none;"></div>
</div>
<div class="ps-card ps-diff-section ps-diff-section-resolved">
<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 class="ps-empty" id="diff-resolved-empty" style="display: none;"></div>
</div>
</div>
<div class="ps-card 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>Endpoint</th><th>Before</th><th>After</th></tr></thead>
<tbody id="diff-sev-body"></tbody>
</table>
</div>
<div class="ps-card 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</th><th>After</th><th>Δ</th></tr></thead>
<tbody id="diff-endp-body"></tbody>
</table>
</div>
</div>
<div id="panel-correlations" role="tabpanel" aria-labelledby="tab-correlations" class="ps-panel ps-panel-narrow" style="display: none;">
<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 class="ps-empty" id="correlations-empty" style="display: none;"></div>
</div>
<div id="panel-green" role="tabpanel" aria-labelledby="tab-green" class="ps-panel ps-panel-narrow" style="display: none;">
<div class="ps-metrics ps-metrics-3" id="green-metrics"></div>
<div id="green-formula" class="ps-formula-bandeau" style="display: none;"></div>
<div id="green-scoring-config" class="ps-scoring-bandeau" style="display: none;" aria-label="Carbon scoring configuration"></div>
<div class="ps-tree-header" id="green-regions-header"></div>
<div class="ps-card" id="green-regions-card" style="display: none;">
<table class="ps-table" id="green-regions-table">
<thead><tr><th>Region</th><th>Intensity (gCO2/kWh)</th><th>I/O ops</th><th>CO2</th><th>Source</th><th>Estimated</th></tr></thead>
<tbody id="green-regions-body"></tbody>
</table>
</div>
<div class="ps-empty" id="green-regions-empty" style="display: none;">No region breakdown available.</div>
</div>
<div id="panel-acknowledgments" role="tabpanel" aria-labelledby="tab-acknowledgments" class="ps-panel ps-panel-narrow" style="display: none;">
<div class="ps-card" id="acks-card" style="display: none;">
<table class="ps-table" id="acks-table">
<thead><tr><th>Signature</th><th style="width: 140px;">By</th><th>Reason</th><th style="width: 120px;">Expires</th><th style="width: 90px;"></th></tr></thead>
<tbody id="acks-body"></tbody>
</table>
</div>
<div class="ps-empty" id="acks-empty" style="display: none;">No daemon acknowledgments active.</div>
<div class="ps-acks-footer" id="acks-footer"></div>
</div>
<div style="height: 28px; flex: none;"></div>
</div>
<footer class="ps-footer">
<span class="ps-kbd">j/k</span> navigate
<span class="ps-kbd">enter</span> open
<span class="ps-kbd">/</span> search
<span class="ps-kbd">esc</span> back
<button type="button" class="ps-kbd" id="footer-shortcuts" style="cursor: pointer; background: transparent;">?</button> shortcuts
<span class="ps-credit">Powered by <a href="https://perf-sentinel.dev/" target="_blank" rel="noopener noreferrer">perf-sentinel</a> · design & logo by <a href="https://www.linkedin.com/in/gwendoline-meignen-b0224873/" target="_blank" rel="noopener noreferrer">Gwendoline Meignen</a></span>
</footer>
</main>
</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>
<dialog id="topic-help" class="ps-modal ps-modal-wide" aria-labelledby="topic-help-title">
<button type="button" class="ps-modal-close" id="topic-help-close" aria-label="Close">x</button>
<h2 id="topic-help-title">Method sheet</h2>
<div id="topic-help-body"></div>
<a id="topic-help-link" class="ps-fix-link" target="_blank" rel="noopener noreferrer">Full methodology ↗</a>
</dialog>
<dialog id="auth-modal" class="ps-modal" aria-labelledby="auth-modal-title">
<button type="button" class="ps-modal-close" id="auth-modal-close" aria-label="Close">x</button>
<h2 id="auth-modal-title">Daemon authentication required</h2>
<p id="auth-modal-hint" style="margin: 0 0 8px 0; font-size: 11px; color: var(--color-text-warning);">The daemon rejected the request with 401. Enter the API key to retry. The key is held in browser memory only for this tab session and cleared when the tab closes.</p>
<form id="auth-modal-form">
<label for="auth-modal-key">API key</label>
<input type="password" id="auth-modal-key" autocomplete="off" />
<div class="ps-modal-error" id="auth-modal-error" role="alert"></div>
<div class="ps-modal-actions">
<button type="button" class="ps-modal-btn" id="auth-modal-cancel">Cancel</button>
<button type="submit" class="ps-modal-btn primary" id="auth-modal-submit">Submit</button>
</div>
</form>
</dialog>
<dialog id="ack-modal" class="ps-modal" aria-labelledby="ack-modal-title">
<button type="button" class="ps-modal-close" id="ack-modal-close" aria-label="Close">x</button>
<h2 id="ack-modal-title">Acknowledge finding</h2>
<form id="ack-modal-form">
<label for="ack-modal-sig">Signature</label>
<input type="text" id="ack-modal-sig" readonly />
<label for="ack-modal-reason">Reason</label>
<textarea id="ack-modal-reason" required></textarea>
<label for="ack-modal-expires">Expires</label>
<select id="ack-modal-expires">
<option value="">Never (permanent)</option>
<option value="24h">24 hours</option>
<option value="7d" selected>7 days</option>
<option value="30d">30 days</option>
</select>
<label for="ack-modal-by">Acknowledged by</label>
<input type="text" id="ack-modal-by" placeholder="optional" />
<div class="ps-modal-error" id="ack-modal-error" role="alert"></div>
<div class="ps-modal-actions">
<button type="button" class="ps-modal-btn" id="ack-modal-cancel">Cancel</button>
<button type="submit" class="ps-modal-btn primary" id="ack-modal-submit">Acknowledge</button>
</div>
</form>
</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", "light", "dark"];
var THEME_LABELS = { auto: "System", light: "Light", dark: "Dark" };
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 themeSvg(children) {
var NS = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(NS, "svg");
var base = { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none",
stroke: "currentColor", "stroke-width": "1.9", "stroke-linecap": "round",
"stroke-linejoin": "round", "class": "ps-th-svg" };
setAttrs(svg, base);
children.forEach(function (c) {
var node = document.createElementNS(NS, c.tag);
setAttrs(node, c.attrs);
svg.appendChild(node);
});
return svg;
}
function themeIcon(mode) {
if (mode === "light") {
return themeSvg([
{ tag: "circle", attrs: { cx: "12", cy: "12", r: "4" } },
{ tag: "path", attrs: { d: "M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" } }
]);
}
if (mode === "dark") {
return themeSvg([{ tag: "path", attrs: { d: "M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" } }]);
}
return themeSvg([
{ tag: "rect", attrs: { x: "3", y: "4", width: "18", height: "13", rx: "2" } },
{ tag: "path", attrs: { d: "M8 21h8M12 17v4" } }
]);
}
function applyTheme(mode, animate) {
document.documentElement.dataset.theme = resolveThemeColor(mode);
var btn = document.getElementById("theme-toggle");
if (btn) {
btn.classList.toggle("ps-anim", !!animate);
btn.textContent = "";
btn.appendChild(themeIcon(mode));
btn.appendChild(el("span", "ps-th-lbl", THEME_LABELS[mode]));
var aria = "Switch theme (current: " + THEME_LABELS[mode] + ")";
btn.title = aria;
btn.setAttribute("aria-label", aria);
}
}
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);
var culpritSpans = payload.culprit_spans || {};
var SEV_RANK = { critical: 0, warning: 1, info: 2 };
function bySeverityDesc(a, b) {
return (SEV_RANK[a.severity] ?? 3) - (SEV_RANK[b.severity] ?? 3);
}
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 setAttrs(node, attrs) { Object.keys(attrs).forEach(function (k) { node.setAttribute(k, String(attrs[k])); }); }
function safeHttpsHref(url) {
if (typeof url !== "string") return null;
if (!url.startsWith("https://")) return null;
if (/[\x00-\x1f\x7f-\x9f]/.test(url)) 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 formatKwh(kwh) {
if (!Number.isFinite(kwh)) return "-";
var abs = Math.abs(kwh);
if (abs >= 0.001 || abs === 0) return kwh.toFixed(3) + " kWh";
return kwh.toExponential(2) + " kWh";
}
function fmtNum(value) {
var parts = String(value).split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
}
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) + "%";
}
function buildKwSet(words) {
var set = Object.create(null);
words.forEach(function (w) { set[w] = true; });
return set;
}
var SQL_KW = buildKwSet([
"SELECT","FROM","WHERE","INSERT","INTO","VALUES","UPDATE","SET","DELETE","AND","OR","ON",
"JOIN","LEFT","RIGHT","INNER","OUTER","GROUP","BY","ORDER","HAVING","LIMIT","OFFSET","LIKE",
"ILIKE","ANY","ALL","AS","NULL","NOT","IN","IS","DISTINCT","EXISTS","BETWEEN","ASC","DESC",
"RETURNING","CREATE","INDEX","USING","TABLE","ALTER","ADD","CONSTRAINT"
]);
var GENERIC_KW = buildKwSet([
"fn","let","mut","pub","use","impl","struct","enum","trait","match","if","else","for","while",
"loop","return","async","await","move","self","Self","as","where","const","static","ref","dyn",
"in","break","continue","unsafe","crate","mod","type",
"func","package","import","var","interface","map","chan","go","defer","range","select","case",
"default","switch","nil",
"function","new","class","extends","implements","export","from","of","typeof","instanceof",
"yield","try","catch","finally","throw","throws","this","null","true","false","void","undefined",
"def","elif","with","except","raise","lambda","not","and","or","is","None","True","False","pass",
"global","del","assert","print",
"public","private","protected","final","abstract","sealed","record","super","synchronized",
"readonly","internal","base","volatile","transient","namespace","using","override","virtual",
"out","params","foreach",
"GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS","TRACE","CONNECT"
]);
function makeTokenSpan(text, color, bold) {
var span = el("span", null, text);
if (color) span.style.color = color;
if (bold) span.style.fontWeight = "600";
return span;
}
function iconSvg(size, d) {
var NS = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(NS, "svg");
var attrs = { width: String(size), height: String(size), viewBox: "0 0 24 24",
fill: "none", stroke: "currentColor", "stroke-width": "3",
"stroke-linecap": "round", "stroke-linejoin": "round", display: "block" };
setAttrs(svg, attrs);
var path = document.createElementNS(NS, "path");
path.setAttribute("d", d);
svg.appendChild(path);
return svg;
}
function sqlHlFragment(frag, sql) {
var re = /(\s+)|('(?:[^']|'')*')|(\$\d+)|(\b\d+\b)|([A-Za-z_][A-Za-z0-9_]*)|([(),.*=<>!|+\/-]+)|(\S)/g;
var toks = [], m;
while ((m = re.exec(sql))) {
if (m[1]) toks.push({ t: "ws", v: m[1] });
else if (m[2]) toks.push({ t: "str", v: m[2] });
else if (m[3]) toks.push({ t: "param", v: m[3] });
else if (m[4]) toks.push({ t: "num", v: m[4] });
else if (m[5]) toks.push({ t: "ident", v: m[5] });
else if (m[6]) toks.push({ t: "op", v: m[6] });
else toks.push({ t: "other", v: m[0] });
}
toks.forEach(function (tk, i) {
var color = null, bold = false;
if (tk.t === "ident") {
if (SQL_KW[tk.v.toUpperCase()]) { color = "var(--info-fg)"; bold = true; }
else {
var j = i + 1;
while (toks[j] && toks[j].t === "ws") j++;
if (toks[j] && toks[j].t === "op" && toks[j].v[0] === "(") color = "var(--warn-fg)";
}
} else if (tk.t === "str") color = "var(--ok-fg)";
else if (tk.t === "param") color = "var(--warn-fg)";
else if (tk.t === "num") color = "var(--ok-fg)";
else if (tk.t === "op") color = "var(--text-3)";
if (color) frag.appendChild(makeTokenSpan(tk.v, color, bold));
else frag.appendChild(document.createTextNode(tk.v));
});
}
function genericHlFragment(frag, src) {
var gre = /(\s+)|(\/\/[^\n]*|#[^\n]*)|(\/\*[\s\S]*?\*\/)|("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)|(@[A-Za-z_][A-Za-z0-9_.]*)|(\b\d+(?:\.\d+)?\b)|([A-Za-z_][A-Za-z0-9_]*!?)|(::|->|=>|[(){}\[\].,:;=<>!&|+\-*\/%?@]+)|(\$[A-Za-z_][A-Za-z0-9_]*)|(\S)/g;
var gtk = [], gm;
while ((gm = gre.exec(src))) {
if (gm[1]) gtk.push({ t: "ws", v: gm[1] });
else if (gm[2] || gm[3]) gtk.push({ t: "cmt", v: gm[2] || gm[3] });
else if (gm[4]) gtk.push({ t: "str", v: gm[4] });
else if (gm[5]) gtk.push({ t: "ann", v: gm[5] });
else if (gm[6]) gtk.push({ t: "num", v: gm[6] });
else if (gm[7]) gtk.push({ t: "id", v: gm[7] });
else if (gm[8]) gtk.push({ t: "op", v: gm[8] });
else if (gm[9]) gtk.push({ t: "var", v: gm[9] });
else gtk.push({ t: "o", v: gm[0] });
}
gtk.forEach(function (x, i) {
var color = null, bold = false;
if (x.t === "cmt") color = "var(--text-3)";
else if (x.t === "ann") color = "var(--warn-fg)";
else if (x.t === "str") color = "var(--ok-fg)";
else if (x.t === "num") color = "var(--ok-fg)";
else if (x.t === "var") color = "var(--warn-fg)"; else if (x.t === "id") {
if (GENERIC_KW[x.v]) { color = "var(--info-fg)"; bold = true; } else {
var j = i + 1;
while (gtk[j] && gtk[j].t === "ws") j++;
var nx = gtk[j] && gtk[j].t === "op" ? gtk[j].v : "";
if (x.v.slice(-1) === "!" || nx[0] === "(" || /_/.test(x.v)) color = "var(--warn-fg)";
else if (/^[A-Z].*[a-z]/.test(x.v)) color = "var(--brand-text)";
}
} else if (x.t === "op") color = "var(--text-3)";
if (color) frag.appendChild(makeTokenSpan(x.v, color, bold));
else frag.appendChild(document.createTextNode(x.v));
});
}
var HTTP_METHODS = { GET: 1, POST: 1, PUT: 1, PATCH: 1, DELETE: 1, HEAD: 1, OPTIONS: 1, TRACE: 1, CONNECT: 1 };
var HTTP_DISPATCH_RE = new RegExp("^\\s*(" + Object.keys(HTTP_METHODS).join("|") + ")\\s+/", "i");
function endpointHlFragment(frag, src) {
var re = /(\s+)|(\{[^}]*\})|(\d+)|([A-Za-z][A-Za-z0-9_-]*)|(\S)/g;
var m;
while ((m = re.exec(src))) {
var color = null, bold = false, v = m[0];
if (m[1]) { frag.appendChild(document.createTextNode(v)); continue; }
if (m[2]) color = "var(--warn-fg)";
else if (m[3]) color = "var(--ok-fg)";
else if (m[4] && HTTP_METHODS[m[4].toUpperCase()]) { color = "var(--info-fg)"; bold = true; }
if (color) frag.appendChild(makeTokenSpan(v, color, bold));
else frag.appendChild(document.createTextNode(v));
}
}
function highlightEndpoint(target, text) {
if (text == null) return;
var frag = document.createDocumentFragment();
endpointHlFragment(frag, String(text));
target.appendChild(frag);
}
function highlightInto(target, text) {
if (text == null) return;
var src = String(text);
var frag = document.createDocumentFragment();
try {
if (HTTP_DISPATCH_RE.test(src)) {
endpointHlFragment(frag, src);
} else if (/^\s*(SELECT|INSERT|UPDATE|DELETE|WITH|CREATE|ALTER)\b/i.test(src)) {
sqlHlFragment(frag, src);
} else {
genericHlFragment(frag, src);
}
} catch (e) {
target.appendChild(document.createTextNode(src));
return;
}
target.appendChild(frag);
}
function addCommaBreaks(root) {
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
var texts = [];
while (walker.nextNode()) texts.push(walker.currentNode);
texts.forEach(function (node) {
var parts = node.nodeValue.split(",");
if (parts.length < 2) return;
var frag = document.createDocumentFragment();
parts.forEach(function (part, i) {
if (i > 0) {
frag.appendChild(document.createTextNode(","));
frag.appendChild(document.createElement("wbr"));
}
if (part) frag.appendChild(document.createTextNode(part));
});
node.parentNode.replaceChild(frag, node);
});
}
function proseInto(target, text) {
if (text == null) return;
var parts = String(text).split(/`([^`]+)`/);
for (var i = 0; i < parts.length; i++) {
if (i % 2 === 1) {
var code = el("code", "ps-code-inline");
highlightInto(code, parts[i]);
target.appendChild(code);
} else if (parts[i]) {
target.appendChild(document.createTextNode(parts[i]));
}
}
}
var SEV_LABEL = { critical: "CRIT", warning: "WARN", info: "INFO" };
var SEV_DATA = { critical: "crit", warning: "warn", info: "info" };
function metaCell(label, value, mono, brand, code) {
var cell = el("div", "ps-meta-cell");
cell.appendChild(el("div", "ps-meta-cell-label", label));
var v = el("div", "ps-meta-cell-value");
if (code) highlightInto(v, value);
else v.textContent = value;
if (mono) v.classList.add("mono");
if (brand) v.classList.add("brand");
cell.appendChild(v);
return cell;
}
function sevPill(severity, cls) {
var pill = el("span", cls || "ps-sev", SEV_LABEL[severity] || "INFO");
setAttr(pill, "data-sev", SEV_DATA[severity] || "info");
return pill;
}
var FINDING_HELP = {
n_plus_one_sql: "The same query shape fired once per row instead of once for the whole set. Flagged when a trace repeats one normalized SQL template at least `[detection] n_plus_one_threshold` times with as many distinct parameter sets, inside `window_duration_ms`. Identical parameters classify as Redundant SQL instead.",
n_plus_one_http: "The same endpoint called once per item instead of once in bulk. Flagged when a trace repeats one normalized HTTP endpoint at least `[detection] n_plus_one_threshold` times with as many distinct parameter sets, inside `window_duration_ms`.",
n_plus_one_messaging: "One broker publish per item instead of a batch. Flagged when a trace repeats one destination at least `[detection] n_plus_one_threshold` times with as many distinct parameter sets, inside `window_duration_ms`.",
redundant_sql: "The exact same query, template and parameters alike, ran more than once in one trace, so the later results could not differ. Two identical calls flag at info, five or more raise a warning: a cache or a local variable would remove them.",
redundant_http: "The exact same HTTP call, endpoint and parameters alike, ran more than once in one trace. Two identical calls flag at info, five or more raise a warning.",
slow_sql: "A query template ran above `[detection] slow_query_threshold_ms`, at least `slow_query_min_occurrences` times. Critical past five times the threshold.",
slow_http: "An HTTP call ran above `[detection] slow_query_threshold_ms`, at least `slow_query_min_occurrences` times. Critical past five times the threshold.",
slow_messaging: "A broker publish ran above `[detection] slow_query_threshold_ms`, at least `slow_query_min_occurrences` times. Critical past five times the threshold.",
excessive_fanout: "One parent span fans out to more child operations than `[detection] max_fanout` allows. A structural shape rather than avoidable I/O: critical past three times the threshold.",
chatty_service: "One trace leaves the service through more outbound HTTP calls than `[detection] chatty_service_min_calls` allows. Batching or a coarser API is the usual fix. Critical past three times the threshold.",
pool_saturation: "The peak number of SQL queries in flight at the same instant reached `[detection] pool_saturation_concurrent_threshold`, enough to contend on the database connection pool. A tuning or architecture signal, not avoidable I/O.",
serialized_calls: "Sibling calls with distinct templates ran strictly one after another (longest non-overlapping chain, at least `[detection] serialized_min_sequential` long) with no visible data dependency: they could run in parallel. Always info, the heuristic cannot see hidden dependencies."
};
var DETECTION_KEY_FIELD = {
n_plus_one_threshold: "n_plus_one_threshold",
window_duration_ms: "window_ms",
slow_query_threshold_ms: "slow_threshold_ms",
slow_query_min_occurrences: "slow_min_occurrences",
max_fanout: "max_fanout",
chatty_service_min_calls: "chatty_service_min_calls",
pool_saturation_concurrent_threshold: "pool_saturation_concurrent_threshold",
serialized_min_sequential: "serialized_min_sequential"
};
function substituteDetectionValues(text) {
var cfg = report.detection_config;
if (!text || !cfg) return text;
Object.keys(DETECTION_KEY_FIELD).forEach(function (key) {
var v = cfg[DETECTION_KEY_FIELD[key]];
if (v === undefined || v === null) return;
text = text.split(key + "`").join(key + " = " + v + "`");
});
return text;
}
function findingHelpText(type) {
return substituteDetectionValues(FINDING_HELP[type]);
}
var METHO_URL = "https://perf-sentinel.dev/docs/METHODOLOGY.html";
var ENERGY_URL = "https://perf-sentinel.dev/docs/ENERGY.html";
var HELP_DEEP = {
n_plus_one_sql: { t: "N+1 SQL", u: METHO_URL, s: [
["What it measures", "One query per row instead of one query for the whole set, the classic lazy-loading pattern: fetching 50 orders then firing 50 `SELECT * FROM order_item WHERE order_id = ?` instead of a single query for all 50."],
["How it is computed", "Literals are normalized away (`WHERE id = 42` becomes `WHERE id = ?`), so queries share a shape. The SQL spans of a trace are grouped by shape, and a group flags when it repeats at least `n_plus_one_threshold` times inside `window_duration_ms`, with as many distinct parameter sets, one query per row. Ten occurrences or more raise it to critical. The same shape with identical parameters is a different smell, Redundant SQL: the results could not differ."],
["What tunes it", "`[detection] n_plus_one_threshold`, `window_duration_ms`, and `sanitizer_aware_classification`, which can keep already-parameterized safe patterns out of the count."],
["Limits", "The detector counts what the trace shows: a driver that batches the queries under the hood, or repeats spread beyond the window, are invisible to it."]
]},
n_plus_one_http: { t: "N+1 HTTP", u: METHO_URL, s: [
["What it measures", "One outbound HTTP call per item instead of one bulk call: fetching a list, then calling `GET /api/users/{id}` once per element."],
["How it is computed", "Paths are normalized (`/users/42` becomes `/users/{id}`) so calls share a template. The trace's outbound HTTP spans are grouped by template, and a group flags when it repeats at least `n_plus_one_threshold` times inside `window_duration_ms` with as many distinct parameter sets. Ten or more is critical."],
["What tunes it", "`[detection] n_plus_one_threshold` and `window_duration_ms`."],
["Limits", "Same-shape calls that a client coalesces or caches downstream still count as distinct occurrences here."]
]},
n_plus_one_messaging: { t: "N+1 messaging", u: METHO_URL, s: [
["What it measures", "One broker publish per item instead of a batched publish."],
["How it is computed", "Publish spans are grouped by destination, and a group flags when it repeats at least `n_plus_one_threshold` times inside `window_duration_ms` with as many distinct parameter sets."],
["What tunes it", "`[detection] n_plus_one_threshold` and `window_duration_ms`."],
["Limits", "A client library may already batch the publishes it buffers before the broker sees them, so the occurrence count is an upper bound on the real waste."]
]},
redundant_sql: { t: "Redundant SQL", u: METHO_URL, s: [
["What it measures", "The exact same query, shape and parameters alike, executed more than once in one trace: the later results could not differ from the first."],
["How it is computed", "Spans are grouped by shape plus the full parameter set. Two identical executions flag at info, five or more raise a warning, the usual signature of a loop bug or a missing cache. Distinct parameters classify as N+1 instead."],
["What tunes it", "Nothing: even two identical queries in one request are worth a look."],
["Limits", "The detector cannot know the data changed between the two executions, a deliberate re-read after a write looks redundant to it."]
]},
redundant_http: { t: "Redundant HTTP", u: METHO_URL, s: [
["What it measures", "The exact same HTTP call, endpoint and parameters alike, fired more than once in one trace."],
["How it is computed", "Outbound spans are grouped by template plus the full parameter set. Two identical calls flag at info, five or more raise a warning. A local variable or a per-request cache removes them."],
["What tunes it", "Nothing: exact duplicates always flag."],
["Limits", "A response that legitimately changes between the two calls is invisible to the detector."]
]},
slow_sql: { t: "Slow SQL", u: METHO_URL, s: [
["What it measures", "A query shape whose executions run above your latency budget."],
["How it is computed", "A shape flags when it runs above `slow_query_threshold_ms` at least `slow_query_min_occurrences` times, so a single outlier does not page anyone. Five times the threshold raises it to critical. Batch runs also compute cross-trace p50/p95/p99 per shape."],
["What tunes it", "`[detection] slow_query_threshold_ms` and `slow_query_min_occurrences`."],
["Limits", "The threshold is absolute and yours to calibrate, the detector has no notion of what is normal for this query."]
]},
slow_http: { t: "Slow HTTP", u: METHO_URL, s: [
["What it measures", "An outbound HTTP endpoint whose calls run above your latency budget."],
["How it is computed", "A template flags when it runs above `slow_query_threshold_ms` at least `slow_query_min_occurrences` times. Five times the threshold raises it to critical."],
["What tunes it", "`[detection] slow_query_threshold_ms` and `slow_query_min_occurrences`."],
["Limits", "Slow findings measure duration, not waste: they carry no avoidable I/O and do not enter the waste ratio."]
]},
slow_messaging: { t: "Slow messaging", u: METHO_URL, s: [
["What it measures", "A broker publish destination whose publishes run above your latency budget."],
["How it is computed", "A destination flags when publishes run above `slow_query_threshold_ms` at least `slow_query_min_occurrences` times. Five times the threshold is critical."],
["What tunes it", "`[detection] slow_query_threshold_ms` and `slow_query_min_occurrences`."],
["Limits", "Publish latency includes broker acknowledgment behavior, which the trace cannot decompose."]
]},
excessive_fanout: { t: "Excessive fanout", u: METHO_URL, s: [
["What it measures", "One parent operation spawning more child calls than a healthy design should need, an aggregator hammering every downstream at once."],
["How it is computed", "Spans are grouped by parent, and a parent flags past `max_fanout` children. Three times the threshold raises it to critical."],
["What tunes it", "`[detection] max_fanout`."],
["Limits", "A structural signal, not avoidable I/O: the fix is architectural (composition, caching, a coarser API), so it does not enter the waste ratio."]
]},
chatty_service: { t: "Chatty service", u: METHO_URL, s: [
["What it measures", "A single request leaving the service through an unreasonable number of outbound HTTP calls, wherever they go."],
["How it is computed", "The trace's outbound HTTP spans are counted, and the trace flags past `chatty_service_min_calls`. Three times the threshold raises it to critical."],
["What tunes it", "`[detection] chatty_service_min_calls`."],
["Limits", "Whole-trace: it says the conversation is too chatty, not which single call to remove. The trace tree highlights all of them."]
]},
pool_saturation: { t: "Pool saturation", u: METHO_URL, s: [
["What it measures", "How many SQL queries were in flight at the same instant, the pressure your database connection pool actually felt."],
["How it is computed", "Query start and end timestamps are swept chronologically to find the peak concurrency, which flags at `pool_saturation_concurrent_threshold`."],
["What tunes it", "`[detection] pool_saturation_concurrent_threshold`."],
["Limits", "High concurrency is not avoidable I/O: it signals pool sizing or architectural contention, so it stays out of the waste ratio."]
]},
serialized_calls: { t: "Serialized calls", u: METHO_URL, s: [
["What it measures", "Sibling calls that ran strictly one after another although nothing visible forces the order: they could run in parallel and the user waits for the sum instead of the max."],
["How it is computed", "Siblings under one parent are sorted by end time and the longest chain of non-overlapping calls with distinct templates is extracted (weighted interval scheduling). It flags at `serialized_min_sequential` links."],
["What tunes it", "`[detection] serialized_min_sequential`."],
["Limits", "Always info on purpose: the trace cannot see a data dependency between the calls, so a legitimate pipeline looks identical to a missed parallelization."]
]},
total_co2: { t: "Total CO2", u: METHO_URL + "#uncertainty-bracket", s: [
["What it measures", "Everything the analyzed traffic emitted, by the SCI v1.0 numerator: operational `(E x I)` plus embodied `M` plus network transport `T`, summed over traces and scored per region when spans carry `cloud.region`."],
["How the bracket works", "low is half the mid value, high is double: a log-symmetric bracket that says the estimate is trusted to a factor of two, not to a percent. The model tag says how the energy figure was obtained, measured by an energy backend or modeled from I/O counts."],
["Limits", "A directional estimate for comparing runs and finding waste, not a wattmeter reading, and the project says so in its methodology."]
]},
operational_co2: { t: "Operational CO2", u: METHO_URL + "#energy-per-operation", s: [
["What it measures", "The carbon of actually running the workload: energy used times the grid carbon intensity of the resolved region."],
["How it is computed", "Energy is either measured (RAPL via Scaphandre, Kepler or Alumet, BMC via Redfish, or the embedded SPECpower table for cloud instances) or modeled from I/O counts with per-operation coefficients. Intensity comes from the embedded per-region table, hourly profiles, or Electricity Maps live when configured."],
["Why it matters", "The only SCI term a code fix can reduce: the avoidable share lives here."]
]},
embodied_co2: { t: "Embodied CO2", u: METHO_URL + "#background-energy-and-sci-primer", s: [
["What it measures", "The manufacturing carbon of the hardware, amortized over the requests it serves: the SCI `M` term."],
["How it is computed", "`[green] embodied_carbon_per_request_gco2` (strictly positive, no hardware has zero embodied carbon) times the number of requests. The applied coefficient is published with every disclosure."],
["Limits", "Real but irreducible by fixing an anti-pattern: it shrinks by serving more with less hardware, not by removing a query."]
]},
avoidable_co2: { t: "Avoidable", u: METHO_URL + "#aggregation-over-a-period", s: [
["What it measures", "The slice of operational carbon your team can actually delete by fixing the avoidable anti-patterns (N+1, redundant calls)."],
["How it is computed", "Operational carbon times the ratio of avoidable I/O operations over accounted ones, the `sci_v1_operational_ratio` methodology. Region-blind and excluding embodied by construction."],
["Why it matters", "This is the number a fix shrinks, and the honest one to celebrate: the total also moves with traffic, this one moves with code quality."]
]},
waste_ratio: { t: "I/O waste ratio", u: METHO_URL + "#io-intensity-score-iis", s: [
["What it measures", "Which share of all I/O operations (SQL, outbound HTTP, publishes) belongs to avoidable anti-patterns."],
["How it is computed", "Avoidable I/O operations, deduplicated across findings so one span never counts twice, divided by total I/O operations. Bands: healthy under 10%, moderate to 30%, high to 50%, critical above."],
["Limits", "Counts operations, not bytes or milliseconds: a tiny redundant query and a huge one weigh the same here."]
]},
iis: { t: "IIS score", u: METHO_URL + "#io-intensity-score-iis", s: [
["What it measures", "How I/O-hungry an endpoint is: its I/O operations per call, averaged over the window. The card shows the worst endpoint."],
["How it is computed", "Per (service, endpoint) pair, total I/O operations divided by invocations. Bands at 2, 5 and 10, and the top offenders ranking orders endpoints by it."],
["Limits", "A relative ranking signal: it tells you where to look first, not how many joules the endpoint burns."]
]},
db_waste: { t: "Database waste", u: ENERGY_URL, s: [
["What it measures", "The avoidable share of the database's own energy, the side of an N+1 the application trace cannot see."],
["How it is computed", "Measured mode: the declared database cgroup's energy (Alumet) times the SQL waste ratio, a CPU-only lower bound that is genuinely additional to the report totals. Estimated mode, on every run without a measured reading: the modeled energy of the window's SQL spans times the same ratio, a re-presented share of the totals, never additional. The model tag says which mode produced the figure."],
["Limits", "Kept out of every total because the database is a different scope than the instrumented services, and the two modes err in different directions."]
]},
broker_waste: { t: "Broker waste", u: ENERGY_URL, s: [
["What it measures", "The avoidable share of the message broker's energy, the twin of database waste for publishes."],
["How it is computed", "Measured on the broker cgroup (Alumet), declared from a provisioned cluster via the embedded SPECpower table, or estimated from the modeled publish energy times the messaging waste ratio. The model tag says which."],
["Limits", "A measurement under-counts (CPU only), a declaration bounds compute at full load without storage or network: the error direction depends on the source, and the figure stays out of every total."]
]},
green_tab: { t: "GreenOps methodology", u: METHO_URL + "#pipeline-at-a-glance", s: [
["The pipeline in one breath", "Count the I/O operations each endpoint performs, turn them into energy (measured by a backend when configured, a per-operation model otherwise), multiply by the grid carbon intensity of each region, then assemble the SCI v1.0 terms: operational plus embodied plus transport."],
["Measured vs modeled", "Every figure carries a model tag naming its source: RAPL, BMC, SPECpower, or the I/O proxy with its factor-of-two bracket. Measured and modeled figures are never silently mixed."],
["What it is for", "A directional waste counter to compare runs and target fixes, published with its uncertainty, not a wattmeter and not a regulatory instrument. The methodology page details every constant and its academic source."]
]}
};
function openTopicHelp(key) {
var d = HELP_DEEP[key];
var dlg = document.getElementById("topic-help");
if (!d || !dlg) return;
document.getElementById("topic-help-title").textContent = d.t;
var body = document.getElementById("topic-help-body");
body.textContent = "";
d.s.forEach(function (sec) {
body.appendChild(el("h3", null, sec[0]));
var para = el("p");
proseInto(para, substituteDetectionValues(sec[1]));
body.appendChild(para);
});
document.getElementById("topic-help-link").href = d.u;
topicHelpPriorFocus = document.activeElement;
dlg.showModal();
var closeBtn = document.getElementById("topic-help-close");
if (closeBtn) closeBtn.focus();
}
var topicHelpPriorFocus = null;
function wireTopicHelpDialog() {
var dlg = document.getElementById("topic-help");
if (!dlg) return;
var close = function () { if (dlg.open) dlg.close(); };
var closeBtn = document.getElementById("topic-help-close");
if (closeBtn) closeBtn.addEventListener("click", close);
dlg.addEventListener("close", function () {
if (topicHelpPriorFocus && topicHelpPriorFocus.focus) topicHelpPriorFocus.focus();
topicHelpPriorFocus = null;
});
dlg.addEventListener("click", function (ev) {
if (ev.target === dlg) close();
});
}
var TYPE_LABEL = {
n_plus_one_sql: "N+1 SQL",
n_plus_one_http: "N+1 HTTP",
n_plus_one_messaging: "N+1 messaging",
redundant_sql: "Redundant SQL",
redundant_http: "Redundant HTTP",
slow_sql: "Slow SQL",
slow_http: "Slow HTTP",
slow_messaging: "Slow messaging",
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); }
var CONFIDENCE_CONTEXT = {
local_batch: { label: "batch · local", tone: "neutral" },
ci_batch: { label: "batch · CI", tone: "neutral" },
daemon_staging: { label: "daemon · live · staging", tone: "warn" },
daemon_production: { label: "daemon · live · prod", tone: "ok" }
};
function renderSidebarContext() {
var ctx = document.getElementById("ps-sidebar-context");
var dot = document.getElementById("ps-sidebar-dot");
if (!ctx) return;
var conf = findings.length ? findings[0].confidence : null;
var meta = CONFIDENCE_CONTEXT[conf];
if (!meta) {
meta = payload.daemon && payload.daemon.url
? { label: "daemon · live", tone: "ok" }
: { label: "batch", tone: "neutral" };
}
ctx.textContent = meta.label;
if (dot) setAttr(dot, "data-ctx", meta.tone);
}
function renderTopbar() {
var verEl = document.getElementById("ps-sidebar-version");
if (verEl) verEl.textContent = "v" + (payload.version || "") + " · AGPL-3.0";
renderSidebarContext();
var gate = report.quality_gate || { passed: true };
var slot = document.getElementById("topbar-gate");
if (slot) {
slot.textContent = "";
slot.appendChild(
el(
"span",
gate.passed ? "ps-gate-pass" : "ps-gate-fail",
gate.passed ? "GATE PASS" : "GATE FAIL"
)
);
}
}
var ROUTE_META = {
overview: { title: "Overview", eyebrow: "summary" },
findings: { title: "Findings", eyebrow: "findings · triage" },
pgstat: { title: "pg_stat", eyebrow: "postgres statements" },
mysqlstat: { title: "mysql_stat", eyebrow: "mysql statement digests" },
diff: { title: "Diff", eyebrow: "diff vs baseline" },
correlations: { title: "Correlations", eyebrow: "cross-trace" },
green: { title: "Carbon", eyebrow: "greenops" },
acknowledgments: { title: "Acknowledgments", eyebrow: "acks" }
};
var TAB_HELP = {
correlations: "A recurring co-occurrence between two findings from different services, computed by the daemon over its rolling window (opt-in via `[daemon.correlation]`). A pair is reported once it repeats at least `min_co_occurrences` times within `lag_threshold_ms` of each other. `conf` is the share of the source finding's occurrences followed by the target, `lag` the median delay between the two, and the trace count how many times the pair co-occurred.",
pgstat: "Server-side statistics from the PostgreSQL `pg_stat_statements` extension: cumulative per-template figures (calls, total and mean time) measured by the database itself, unlike findings, which come from traces. Loaded with `--pg-stat <file>` or `--pg-stat-prometheus <url>`, top entries per ranking. A SQL span in Explain whose template matches a row links to this tab.",
mysqlstat: "Server-side statistics from the MySQL Performance Schema (`events_statements_summary_by_digest`): cumulative per-digest figures measured by the database itself. Loaded with `--mysql-stat <file>`, with the same ranking switcher as pg_stat.",
green: "GreenOps scoring on the SCI v1.0 methodology: carbon is `(E x I) + M + T`, energy times grid intensity, plus embodied hardware, plus network transport, summed over the analyzed traffic. The model tag says whether the energy was measured (RAPL, BMC, SPECpower) or modeled from I/O counts. Each card carries its own help."
};
function updateTopbarTitle(key) {
var meta = ROUTE_META[key] || { title: key, eyebrow: "" };
var eb = document.getElementById("topbar-eyebrow");
var h1 = document.getElementById("topbar-h1");
if (eb) eb.textContent = "// " + meta.eyebrow;
if (h1) {
h1.textContent = meta.title;
var row = h1.parentElement;
sweepTips();
row.querySelectorAll(".ps-nav-help").forEach(function (n) { n.remove(); });
if (TAB_HELP[key]) {
var help = el("span", "ps-nav-help", "?");
setAttr(help, "aria-label", TAB_HELP[key]);
wireTip(help, TAB_HELP[key], key === "green" ? "green_tab" : null);
row.appendChild(help);
}
}
}
var tabs = [];
var registeredTabs = Object.create(null);
function registerTab(key, label, countFn) {
tabs.push({ key: key, label: label, countFn: countFn });
registeredTabs[key] = true;
}
function registerDisabledTab(key, label, tip) {
tabs.push({ key: key, label: label, disabledTip: tip });
}
function sweepTips() {
document.querySelectorAll(".ps-tipbox").forEach(function (b) { b.remove(); });
}
var tipSweepWired = false;
function wireTip(anchor, text, deepKey) {
if (!tipSweepWired) {
tipSweepWired = true;
window.addEventListener("scroll", sweepTips, true);
document.addEventListener("click", sweepTips, true);
}
setAttr(anchor, "role", "button");
setAttr(anchor, "tabindex", "0");
var box = null;
function showTip() {
sweepTips();
box = el("div", "ps-tipbox");
proseInto(box, text);
if (deepKey) {
box.appendChild(el("div", "ps-tipbox-more", "Click the ? for the full method sheet"));
}
document.body.appendChild(box);
var r = anchor.getBoundingClientRect();
var w = box.offsetWidth;
var h = box.offsetHeight;
var centeredTop = r.top + r.height / 2 - h / 2;
if (centeredTop < 8) {
box.classList.add("ps-tipbox-below");
var left = Math.max(8, Math.min(r.left + r.width / 2 - w / 2, innerWidth - w - 8));
box.style.left = Math.round(left) + "px";
box.style.top = Math.round(r.bottom + 9) + "px";
box.style.setProperty("--tip-caret-x", Math.round(r.left + r.width / 2 - left - 4) + "px");
} else {
box.style.left = Math.round(Math.max(8, Math.min(r.right + 10, innerWidth - w - 8))) + "px";
box.style.top = Math.round(Math.min(centeredTop, innerHeight - h - 8)) + "px";
}
}
function hideTip() {
if (box) { box.remove(); box = null; }
}
anchor.addEventListener("mouseenter", showTip);
anchor.addEventListener("mouseleave", hideTip);
anchor.addEventListener("focus", showTip);
anchor.addEventListener("blur", hideTip);
if (deepKey) {
anchor.addEventListener("click", function (ev) {
ev.stopPropagation();
sweepTips();
openTopicHelp(deepKey);
});
anchor.addEventListener("keydown", function (ev) {
if (ev.key !== "Enter" && ev.key !== " ") return;
ev.preventDefault();
ev.stopPropagation();
sweepTips();
openTopicHelp(deepKey);
});
}
}
function tabIsRegistered(key) { return !!registeredTabs[key]; }
function renderTabs() {
var bar = document.getElementById("tabs");
tabs.forEach(function (t) {
var btn = el("button", "ps-tab");
btn.type = "button";
btn.appendChild(el("span", "ps-nav-dot"));
btn.appendChild(el("span", "ps-nav-label", t.label));
if (t.disabledTip) {
setAttr(btn, "data-nav", "");
btn.classList.add("ps-tab-disabled");
setAttr(btn, "aria-disabled", "true");
var help = el("span", "ps-nav-help", "?");
setAttr(help, "aria-label", t.disabledTip);
wireTip(help, t.disabledTip);
btn.appendChild(help);
bar.appendChild(btn);
return;
}
setAttr(btn, "data-nav", "");
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) {
btn.appendChild(el("span", "ps-nav-badge", String(c)));
}
}
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) {
currentTab = key;
document.querySelectorAll("#tabs [role=\"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";
});
updateTopbarTitle(key);
applyGlobalSearch();
if (key === "findings" && selectedFinding && selectedFinding !== detailFinding) {
openExplain(selectedFinding);
}
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 tonedValue(card, tone) {
var v = card.querySelector(".ps-metric-value");
if (v) setAttr(v, "data-tone", tone);
setAttr(card, "data-grad", tone);
return card;
}
function wireKpiCard(card, tabKey, chipKey) {
setAttr(card, "data-kpi-link", "");
setAttr(card, "role", "button");
card.tabIndex = 0;
var label = card.querySelector(".ps-metric-label");
setAttr(card, "aria-label", (label ? label.textContent + ": " : "") + "go to " + tabKey);
function activate() {
clearSearchQuery();
switchTab(tabKey);
if (!chipKey) return;
var chip = document.querySelector('#findings-filters .ps-chip[data-key="' + chipKey + '"]');
if (chip && chip.getAttribute("aria-checked") !== "true") chip.click();
}
card.addEventListener("click", activate);
card.addEventListener("keydown", function (ev) {
if (ev.key === "Enter" || ev.key === " ") {
ev.preventDefault();
activate();
}
});
return card;
}
function renderFindingsMetrics() {
var root = document.getElementById("findings-metrics");
root.textContent = "";
var sev = countBySeverity();
var total = findings.length;
var traces = (report.analysis && report.analysis.traces_analyzed) || 0;
var kpiTone = total === 0 ? "ok"
: sev.critical > 0 ? "crit"
: sev.warning > 0 ? "warn"
: "info";
var findingsCard = metricCard("Findings", fmtNum(total), "across " + fmtNum(traces) + " traces");
setAttr(findingsCard, "data-kpi", kpiTone);
root.appendChild(wireKpiCard(findingsCard, "findings", "all"));
var domCard, domChip;
if (sev.critical > 0) {
domCard = tonedValue(
metricCard("Critical", fmtNum(sev.critical), fmtNum(sev.warning) + " warn · " + fmtNum(sev.info) + " info"),
"crit"
);
domChip = "sev:critical";
} else if (sev.warning > 0) {
domCard = tonedValue(metricCard("Warn", fmtNum(sev.warning), fmtNum(sev.info) + " info"), "warn");
domChip = "sev:warning";
} else if (sev.info > 0) {
domCard = tonedValue(metricCard("Info", fmtNum(sev.info), ""), "info");
domChip = "sev:info";
} else {
domCard = tonedValue(metricCard("Critical", "0", "0 warn · 0 info"), "ok");
domChip = "all";
}
root.appendChild(wireKpiCard(domCard, "findings", domChip));
if (hasDiff) {
var nNew = (diffData.new_findings || []).length;
var nRes = (diffData.resolved_findings || []).length;
var dCard = metricCard("Δ Baseline", "", "new / resolved");
var dv = dCard.querySelector(".ps-metric-value");
dv.appendChild(makeTokenSpan("+" + nNew, "var(--crit-fg)", false));
dv.appendChild(document.createTextNode(" / "));
dv.appendChild(makeTokenSpan("−" + nRes, "var(--ok-fg)", false));
if (nNew !== nRes) setAttr(dCard, "data-grad", nNew > nRes ? "crit" : "ok");
root.appendChild(wireKpiCard(dCard, "diff"));
} else {
root.appendChild(metricCard("Δ Baseline", "-", "no baseline"));
}
if (hasGreen) {
var co2 = greenSummary.co2;
root.appendChild(wireKpiCard(tonedValue(
metricCard("Total CO2", formatGco2(co2.total.mid),
"low " + formatGco2(co2.total.low) + " · high " + formatGco2(co2.total.high)),
"brand"
), "green"));
} else {
root.appendChild(metricCard("Total CO2", "-", "green disabled"));
}
}
function formatRuleNumber(n) {
if (!Number.isFinite(n)) return "-";
if (Number.isInteger(n)) return String(n);
return n.toFixed(3);
}
var GATE_RULE_LABELS = {
n_plus_one_sql_critical_max: "Max critical N+1 SQL",
n_plus_one_http_warning_max: "Max N+1 HTTP (warning+)",
n_plus_one_messaging_warning_max: "Max N+1 messaging (warning+)",
io_waste_ratio_max: "Max I/O waste ratio",
min_usable_span_ratio: "Min usable span ratio"
};
function humanizeGateRule(key) {
if (Object.prototype.hasOwnProperty.call(GATE_RULE_LABELS, key)) return GATE_RULE_LABELS[key];
var s = String(key || "");
var prefix = "";
if (s.endsWith("_max")) { prefix = "Max "; s = s.slice(0, -4); }
else if (s.endsWith("_min")) { prefix = "Min "; s = s.slice(0, -4); }
s = s.replace(/_/g, " ")
.replace(/\bn plus one\b/g, "N+1")
.replace(/\bsql\b/gi, "SQL")
.replace(/\bhttp\b/gi, "HTTP")
.replace(/\bio\b/gi, "I/O");
if (!prefix && s) s = s.charAt(0).toUpperCase() + s.slice(1);
return prefix + s;
}
function renderOverviewPanel() {
renderOverviewHero();
renderOverviewBody();
}
function renderOverviewHero() {
var host = document.getElementById("overview-hero");
if (!host) return;
host.textContent = "";
var gate = report.quality_gate || { passed: true, rules: [] };
var rules = gate.rules || [];
var card = el("section", "ps-card ps-hero");
var left = el("div", "ps-hero-left");
setAttr(left, "data-tone", gate.passed ? "ok" : "crit");
left.appendChild(el("div", "ps-eyebrow", "// quality gate"));
var verdict = el("div", "ps-hero-verdict");
setAttr(verdict, "data-tone", gate.passed ? "ok" : "crit");
verdict.appendChild(el("span", "ps-hero-dot"));
verdict.appendChild(el("span", "ps-hero-big", gate.passed ? "PASS" : "FAIL"));
left.appendChild(verdict);
var failed = rules.filter(function (r) { return !r.passed; }).length;
left.appendChild(el(
"p",
"ps-hero-line",
gate.passed
? "All " + rules.length + " gate rules satisfied."
: failed + " of " + rules.length + " gate rules failed."
));
card.appendChild(left);
var right = el("div", "ps-hero-right");
if (rules.length > 0) {
var grid = el("div", "ps-hero-rules");
rules.forEach(function (r) {
var item = el("div", "ps-hero-rule");
var chip = el("span", "ps-hero-rule-chip");
setAttr(chip, "data-sev", r.passed ? "ok" : "crit");
chip.appendChild(iconSvg(10, r.passed ? "M5 12l4.5 4.5L19 6" : "M6 6l12 12M18 6L6 18"));
item.appendChild(chip);
var txt = el("div", "ps-hero-rule-text");
txt.appendChild(el("span", "ps-hero-rule-name", humanizeGateRule(r.rule)));
txt.appendChild(
el("span", "ps-hero-rule-detail",
formatRuleNumber(r.actual) + " / " + formatRuleNumber(r.threshold))
);
item.appendChild(txt);
grid.appendChild(item);
});
right.appendChild(grid);
}
var analysis = report.analysis || {};
var meta = el("div", "ps-hero-meta");
[
payload.input_label || "-",
fmtNum(analysis.traces_analyzed || 0) + " traces",
fmtNum(analysis.events_processed || 0) + " spans",
"v" + (payload.version || "")
].forEach(function (m) { meta.appendChild(el("span", null, m)); });
right.appendChild(meta);
var ingest = analysis.ingest;
if (ingest && ingest.spans_filtered > 0) {
var gaps = (ingest.filtered_missing_db_statement || 0)
+ (ingest.filtered_missing_http_url || 0);
var txt = fmtNum(ingest.spans_received || 0) + " spans ingested, "
+ fmtNum(ingest.spans_filtered) + " filtered";
if (gaps > 0) txt += ", " + fmtNum(gaps) + " missing db.statement or http.url";
var warn = el("div", "ps-hero-ingest", txt);
if (gaps > 0) {
setAttr(warn, "data-gap", "true");
warn.title = "Spans that are I/O operations but shipped without the attribute "
+ "perf-sentinel needs. A thin report may mean unusable instrumentation, "
+ "not a clean service.";
}
right.appendChild(warn);
}
card.appendChild(right);
host.appendChild(card);
}
function renderOverviewBody() {
var host = document.getElementById("overview-body");
if (!host) return;
host.textContent = "";
if (findings.length === 0) return;
var grid = el("div", "ps-overview-cols");
var leftCard = el("div", "ps-card");
var head = el("div", "ps-overview-card-head");
head.appendChild(el("span", "ps-overview-card-title", "Top findings by impact"));
var viewAll = el("button", "ps-overview-link");
viewAll.appendChild(el("span", "ps-link-txt", "view all"));
viewAll.appendChild(document.createTextNode(" →"));
viewAll.type = "button";
viewAll.addEventListener("click", function () { switchTab("findings"); });
head.appendChild(viewAll);
leftCard.appendChild(head);
var totalIo = ioShareBase();
var list = el("div", "ps-list");
var topByImpact = findings.slice().sort(function (a, b) {
var sa = SEV_RANK[a.severity] ?? 3, sb = SEV_RANK[b.severity] ?? 3;
if (sa !== sb) return sa - sb;
var ga = a.green_impact || {}, gb = b.green_impact || {};
var ia = ga.estimated_extra_io_ops || 0, ib = gb.estimated_extra_io_ops || 0;
if (ia !== ib) return ib - ia;
return (gb.io_intensity_score || 0) - (ga.io_intensity_score || 0);
});
topByImpact.slice(0, 5).forEach(function (f) {
var row = el("div", "ps-row ps-overview-finding");
var left = el("div", "ps-of-left");
var top = el("div", "ps-fin-topline");
top.appendChild(sevPill(f.severity));
var occ = (f.pattern && f.pattern.occurrences) || 0;
var typeText = typeLabel(f.type || f.finding_type);
top.appendChild(el("span", "ps-fin-type", occ ? typeText + " · ×" + occ : typeText));
left.appendChild(top);
var ovSub = el("span", "ps-fin-service ps-fin-sub");
highlightEndpoint(ovSub, (f.service || "-") + " · " + (f.source_endpoint || "-"));
left.appendChild(ovSub);
row.appendChild(left);
var gi = f.green_impact || {};
var right = el("div", "ps-of-right");
if (Number.isFinite(gi.io_intensity_score)) {
right.appendChild(el("span", "ps-of-primary", "IIS " + gi.io_intensity_score.toFixed(1)));
}
if (totalIo > 0 && Number.isFinite(gi.estimated_extra_io_ops)) {
right.appendChild(el(
"span",
"ps-of-secondary",
Math.round((gi.estimated_extra_io_ops / totalIo) * 100) + "% of I/O"
));
}
row.appendChild(right);
row.addEventListener("click", function () { gotoFinding(f); });
list.appendChild(row);
});
leftCard.appendChild(list);
grid.appendChild(leftCard);
var rail = el("div", "ps-overview-rail");
if (hasDiff) rail.appendChild(buildDiffMini());
if (hasGreen) rail.appendChild(buildCarbonMini());
if (rail.childNodes.length > 0) grid.appendChild(rail);
host.appendChild(grid);
}
function gotoFinding(f) {
clearSearchQuery();
switchTab("findings");
var idx = visibleFindings.indexOf(f);
if (idx < 0) {
filterState.severity = null;
filterState.service = null;
syncFilterChips();
if (findingHiddenByAck(f)) {
var toggle = document.getElementById("findings-include-acked");
if (toggle) toggle.checked = true;
}
var pos = applyFilters().indexOf(f);
shownCount = pos >= 0 ? Math.max(LIST_CAP, pos + 1) : LIST_CAP;
renderFindingsList();
idx = visibleFindings.indexOf(f);
}
if (idx >= 0) {
selectFinding(idx);
scrollDetailToTop();
}
}
function openExternalFindingDetail(f) {
clearSearchQuery();
switchTab("findings");
selectedIndex = -1;
selectedFinding = null;
syncSelection();
openExplain(f);
}
function buildDiffMini() {
var card = el("div", "ps-mini-card");
card.appendChild(el("span", "ps-mini-title", "Diff vs Baseline"));
var pills = el("div", "ps-mini-pills");
var nNew = (diffData.new_findings || []).length;
var nResolved = (diffData.resolved_findings || []).length;
var nChanged = (diffData.severity_changes || []).length;
var pNew = el("span", "ps-mini-pill", "+" + nNew + " new");
setAttr(pNew, "data-sev", "crit");
var pRes = el("span", "ps-mini-pill", "−" + nResolved + " resolved");
setAttr(pRes, "data-sev", "ok");
var pChg = el("span", "ps-mini-pill", nChanged + " changed");
setAttr(pChg, "data-sev", "warn");
pills.appendChild(pNew);
pills.appendChild(pRes);
pills.appendChild(pChg);
card.appendChild(pills);
var link = el("button", "ps-mini-link");
link.appendChild(el("span", "ps-link-txt", "open diff"));
link.appendChild(document.createTextNode(" →"));
link.type = "button";
link.addEventListener("click", function () { switchTab("diff"); });
card.appendChild(link);
return card;
}
function buildCarbonMini() {
var card = el("div", "ps-mini-card");
var head = el("div", "ps-mini-head");
head.appendChild(el("span", "ps-mini-title", "Carbon by region"));
var co2 = greenSummary.co2 || {};
var total = typeof co2.operational_gco2 === "number"
? co2.operational_gco2
: (greenSummary.regions || []).reduce(function (s, r) { return s + (r.co2_gco2 || 0); }, 0);
head.appendChild(el("span", "ps-mini-total", formatGco2(total)));
card.appendChild(head);
var regions = (greenSummary.regions || []).slice();
regions.sort(function (a, b) { return (b.co2_gco2 || 0) - (a.co2_gco2 || 0); });
var top = regions.slice(0, 3);
top.forEach(function (r) {
var row = el("div", "ps-mini-row");
var rh = el("div", "ps-mini-row-head");
rh.appendChild(el("span", null, r.region || "-"));
rh.appendChild(el("span", null, formatGco2(r.co2_gco2 || 0)));
row.appendChild(rh);
var track = el("div", "ps-bar-track");
var fill = el("div", "ps-bar-fill");
fill.style.width = percent(r.co2_gco2 || 0, total);
track.appendChild(fill);
row.appendChild(track);
card.appendChild(row);
});
return card;
}
var filterState = { severity: null, service: null };
var sortState = { key: "severity", dir: "desc" };
function severityRankOf(f) {
return SEV_RANK[f.severity] ?? 3;
}
function compareFindings(a, b) {
var primary, tie;
if (sortState.key === "impact") {
primary = recurrenceOf(b).ops - recurrenceOf(a).ops;
tie = severityRankOf(a) - severityRankOf(b);
} else {
primary = severityRankOf(a) - severityRankOf(b);
tie = recurrenceOf(b).ops - recurrenceOf(a).ops;
}
if (sortState.dir === "asc") primary = -primary;
return primary || tie;
}
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);
function appendChip(parent, label, key, chipRole, stateAttr, sev) {
var btn = el("button", "ps-chip", label);
btn.type = "button";
setAttr(btn, "role", chipRole);
setAttr(btn, "data-key", key);
setAttr(btn, stateAttr, "false");
if (sev) setAttr(btn, "data-sev", sev);
btn.addEventListener("click", function () { onFilterClick(key); });
parent.appendChild(btn);
}
var counts = countBySeverity();
appendChip(severityGroup, "All", "all", "radio", "aria-checked");
if (counts.critical > 0) appendChip(severityGroup, "critical " + counts.critical, "sev:critical", "radio", "aria-checked", "crit");
if (counts.warning > 0) appendChip(severityGroup, "warnings " + counts.warning, "sev:warning", "radio", "aria-checked", "warn");
if (counts.info > 0) appendChip(severityGroup, "info " + counts.info, "sev:info", "radio", "aria-checked", "info");
var counter = el("span", "ps-findings-count");
setAttr(counter, "id", "findings-count");
root.appendChild(counter);
var serviceGroup = el("span", "ps-chip-group", null);
setAttr(serviceGroup, "role", "group");
setAttr(serviceGroup, "aria-label", "Finding service");
root.appendChild(serviceGroup);
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");
});
var sortGroup = el("span", "ps-chip-group ps-sort-group", null);
setAttr(sortGroup, "role", "group");
setAttr(sortGroup, "aria-label", "Sort findings");
sortGroup.appendChild(el("span", "ps-sort-label", "sort"));
["severity", "impact"].forEach(function (key) {
var btn = el("button", "ps-chip");
btn.type = "button";
setAttr(btn, "data-sort-key", key);
setAttr(btn, "aria-pressed", "false");
btn.addEventListener("click", function () { onSortClick(key); });
sortGroup.appendChild(btn);
});
root.appendChild(sortGroup);
syncFilterChips();
syncSortChips();
}
function onSortClick(key) {
if (sortState.key === key) {
sortState.dir = sortState.dir === "desc" ? "asc" : "desc";
} else {
sortState.key = key;
sortState.dir = "desc";
}
syncSortChips();
renderFindingsList();
}
function syncSortChips() {
document.querySelectorAll("[data-sort-key]").forEach(function (btn) {
var key = btn.getAttribute("data-sort-key");
var active = sortState.key === key;
btn.classList.toggle("ps-sort-on", active);
setAttr(btn, "aria-pressed", active ? "true" : "false");
btn.textContent = active ? key + " " + (sortState.dir === "desc" ? "↓" : "↑") : key;
btn.title = active
? "Click to flip the direction"
: (key === "impact"
? "Rank by each problem's total avoidable I/O across its detections"
: "Rank by each detection's own severity");
});
}
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) {
if (chip.dataset.sortKey) return;
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 = 8;
var visibleFindings = [];
var selectedIndex = -1;
var selectedFinding = null;
var detailFinding = null;
var shownCount = LIST_CAP;
function selectFinding(idx) {
if (idx < 0 || idx >= visibleFindings.length) return;
if (idx === selectedIndex && visibleFindings[idx] === selectedFinding) return;
selectedIndex = idx;
selectedFinding = visibleFindings[idx];
syncSelection();
openExplain(selectedFinding);
}
function anyFindingsFilterActive() {
if (filterState.severity || filterState.service || globalSearchTerm()) return true;
return findings.some(findingHiddenByAck);
}
function findingHiddenByAck(f) {
if (!isLiveEnabled()) return false;
var input = document.getElementById("findings-include-acked");
if (!input || input.checked) return false;
return !!(f.signature && liveState.acksBySig[f.signature]);
}
function applyFilters() {
var term = globalSearchTerm();
return findings.filter(function (f) {
if (filterState.severity && f.severity !== filterState.severity) return false;
if (filterState.service && f.service !== filterState.service) return false;
if (findingHiddenByAck(f)) return false;
return findingMatchesSearch(f, term);
}).sort(compareFindings);
}
function renderFindingsList() {
var root = document.getElementById("findings-list");
var empty = document.getElementById("findings-empty");
root.textContent = "";
var filtered = applyFilters();
visibleFindings = filtered.slice(0, shownCount);
if (visibleFindings.length === 0) {
selectedFinding = null;
selectedIndex = -1;
empty.textContent = anyFindingsFilterActive()
? "No findings match the current filters."
: "No findings in this trace set.";
empty.style.display = "";
resetDetailPane();
syncShowMore(filtered.length);
reapplySearchForTab("findings", filtered.length);
return;
}
empty.style.display = "none";
visibleFindings.forEach(function (f, idx) {
root.appendChild(buildFindingRow(f, idx));
});
selectedIndex = selectedFinding ? visibleFindings.indexOf(selectedFinding) : -1;
if (selectedIndex < 0) {
selectedIndex = 0;
selectedFinding = visibleFindings[0];
}
syncSelection();
if (currentTab === "findings" && selectedFinding !== detailFinding) {
openExplain(selectedFinding);
}
syncShowMore(filtered.length);
reapplySearchForTab("findings", filtered.length);
syncFindingActionButtons();
}
function resetDetailPane() {
detailFinding = null;
var emptyState = document.getElementById("explain-empty");
if (emptyState) emptyState.style.display = "";
var head = document.getElementById("explain-detail-head");
if (head) head.textContent = "";
var content = document.getElementById("explain-content");
if (content) content.style.display = "none";
var ne = document.getElementById("explain-not-embedded");
if (ne) { ne.style.display = "none"; ne.textContent = ""; }
}
function syncShowMore(totalMatching) {
var counter = document.getElementById("findings-count");
if (counter) {
counter.textContent = totalMatching + " findings · " + visibleFindings.length + " shown";
}
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);
if (f.signature) setAttr(row, "data-sig", f.signature);
var topLine = el("div", "ps-fin-topline");
topLine.appendChild(sevPill(f.severity));
topLine.appendChild(el("span", "ps-fin-type", typeLabel(f.type || f.finding_type)));
var occ = (f.pattern && f.pattern.occurrences) || 0;
var agg = sortState.key === "impact" ? recurrenceOf(f) : null;
if (agg && agg.ops > 0) {
var metric = el("span", "ps-fin-right ps-fin-metric", "~" + fmtNum(agg.ops) + " ops");
metric.title = "detected in " + agg.count + (agg.count > 1 ? " traces" : " trace") +
" · " + fmtNum(agg.ops) + " avoidable operations in total";
topLine.appendChild(metric);
} else if (occ) {
topLine.appendChild(el("span", "ps-fin-right ps-fin-metric", "×" + occ));
}
row.appendChild(topLine);
var finSub = el("span", "ps-fin-service ps-fin-sub");
highlightEndpoint(finSub, (f.service || "-") + " · " + (f.source_endpoint || "-"));
row.appendChild(finSub);
var tpl = (f.pattern && f.pattern.template) || "";
if (tpl) row.appendChild(el("span", "ps-sr-only", tpl));
var actions = el("div", "ps-fin-actions");
var btn = el("button", "ps-fin-action-btn", "Ack");
btn.type = "button";
setAttr(btn, "data-action", "ack");
setAttr(btn, "aria-label", "Acknowledge this finding");
actions.appendChild(btn);
row.appendChild(actions);
row.addEventListener("click", function (event) {
var t = event.target;
if (t && t.closest && t.closest(".ps-fin-action-btn")) {
return;
}
selectFinding(idx);
scrollDetailToTop();
});
return row;
}
function syncSelection() {
document.querySelectorAll("#findings-list .ps-row").forEach(function (row, idx) {
row.classList.toggle("selected", idx === selectedIndex);
});
}
function findingSummary(f) {
if (f.message) return f.message;
if (f.description) return f.description;
var n = (f.pattern && f.pattern.occurrences) || 0;
switch (f.type || f.finding_type) {
case "n_plus_one_sql": return n + " near-identical queries fired within a single trace.";
case "n_plus_one_http": return n + " near-identical HTTP calls fired within a single trace.";
case "n_plus_one_messaging": return n + " messages published to one destination within a single trace.";
case "redundant_sql": return n + " duplicate queries with identical parameters.";
case "redundant_http": return n + " duplicate HTTP calls with identical parameters.";
case "slow_sql": return "A query exceeding the latency budget.";
case "slow_http": return "An HTTP call exceeding the latency budget.";
case "slow_messaging": return "A message publish exceeding the latency budget.";
case "excessive_fanout": return n + " child calls fanned out from one parent span.";
case "chatty_service": return n + " calls to the same downstream within a single trace.";
case "pool_saturation": return "Connection pool saturated during the trace.";
case "serialized_calls": return n + " calls run serially that could overlap.";
default: return "";
}
}
function isDaemonProducedRun() {
var conf = findings.length ? findings[0].confidence : null;
return conf === "daemon_staging" || conf === "daemon_production";
}
function ioShareBase() {
if (isDaemonProducedRun()) return 0;
return (greenSummary && greenSummary.total_io_ops) || 0;
}
function ioImpactText(f) {
var gi = f.green_impact;
if (!gi) return "-";
var totalIo = ioShareBase();
if (totalIo > 0 && Number.isFinite(gi.estimated_extra_io_ops)) {
return Math.round((gi.estimated_extra_io_ops / totalIo) * 100) + "% of I/O";
}
return Number.isFinite(gi.io_intensity_score) ? "IIS " + gi.io_intensity_score.toFixed(1) : "-";
}
function shortTraceId(traceId) {
if (!traceId) return "-";
return traceId.length > 12 ? traceId.slice(0, 12) + "…" : traceId;
}
function appendCopyTraceIdButton(host, traceId) {
if (!traceId) return;
var btn = el("button", "ps-copy-link-btn ps-copy-trace", "Copy trace_id");
btn.type = "button";
btn.addEventListener("click", function () { copyTextToClipboard(btn, traceId); });
host.appendChild(btn);
}
function renderDetailHead(f) {
var head = document.getElementById("explain-detail-head");
head.textContent = "";
var headRow = el("div", "ps-detail-head");
headRow.appendChild(sevPill(f.severity));
var titleWrap = el("div", "ps-detail-titlewrap");
var titleRow = el("div", "ps-title-row");
var h2 = el("h2", "ps-detail-h2", typeLabel(f.type || f.finding_type));
titleRow.appendChild(h2);
var helpText = findingHelpText(f.type || f.finding_type);
if (helpText) {
var h2help = el("span", "ps-nav-help", "?");
setAttr(h2help, "aria-label", helpText);
wireTip(h2help, helpText, f.type || f.finding_type);
titleRow.appendChild(h2help);
}
titleWrap.appendChild(titleRow);
var sub = findingSummary(f);
if (sub) titleWrap.appendChild(el("p", "ps-detail-sub", sub));
headRow.appendChild(titleWrap);
head.appendChild(headRow);
var grid = el("div", "ps-meta-grid");
grid.appendChild(metaCell("service", f.service || "-"));
grid.appendChild(metaCell("endpoint", f.source_endpoint || "-", true, false, true));
grid.appendChild(metaCell("i/o impact", ioImpactText(f)));
var occ = (f.pattern && f.pattern.occurrences) || 0;
grid.appendChild(metaCell("occurrences", occ ? ("×" + occ) : "-"));
head.appendChild(grid);
if (f.suggestion) {
var sugSec = el("div", "ps-section");
sugSec.appendChild(el("span", "ps-eyebrow", "// suggestion"));
var sugBody = el("p", "ps-detail-suggestion");
proseInto(sugBody, f.suggestion);
sugSec.appendChild(sugBody);
head.appendChild(sugSec);
}
var tpl = (f.pattern && f.pattern.template) || "";
if (tpl) {
var sec = el("div", "ps-section");
sec.appendChild(el("span", "ps-eyebrow", "// normalized template"));
var code = el("code", "ps-codeblock");
highlightInto(code, tpl);
addCommaBreaks(code);
sec.appendChild(code);
head.appendChild(sec);
}
}
function openExplain(f) {
sweepTips();
detailFinding = 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 = "";
renderDetailHead(f);
var trace = tracesById[f.trace_id];
content.style.display = "";
var breadcrumb = document.getElementById("explain-breadcrumb");
breadcrumb.textContent = "";
highlightEndpoint(
breadcrumb,
"trace_id = " + shortTraceId(f.trace_id) + " · " + (f.service || "-") + " · " + (f.source_endpoint || "-")
);
if (f.trace_id) breadcrumb.title = "trace_id = " + f.trace_id;
appendCopyTraceIdButton(breadcrumb, f.trace_id);
var treeRoot = document.getElementById("explain-tree");
treeRoot.textContent = "";
treeRoot.style.display = trace ? "" : "none";
if (trace) {
renderTree(treeRoot, trace, f);
} else {
notEmbedded.style.display = "";
if (payload.trimmed_traces) {
notEmbedded.textContent = "Trace not embedded (trimmed to fit the size budget). Rerun with --max-traces-embedded <higher> to include it.";
} else if (embeddedTraces.length === 0) {
notEmbedded.textContent = "No span tree in this report: it was built from a Report JSON (a daemon snapshot or a saved baseline), which carries findings but not spans. Use the trace_id above against a live daemon: perf-sentinel query --daemon <URL> explain <trace_id>.";
} else if (isDaemonProducedRun()) {
notEmbedded.textContent = "Trace not in this snapshot: it aged out of the daemon's trace retention ([daemon] max_retained_traces) or was dropped by the export size cap. Recent findings keep their trees; for this one, use the trace_id above against the live daemon: perf-sentinel query --daemon <URL> explain <trace_id>.";
} else {
notEmbedded.textContent = "Trace not embedded in this report.";
}
}
var fix = document.getElementById("explain-fix");
fix.textContent = "";
var hasFix = !!f.suggested_fix;
if (hasFix) {
fix.style.display = "";
renderSuggestedFix(fix, f.suggested_fix);
} else {
fix.style.display = "none";
}
var hasPgstat = renderPgStatXref(document.getElementById("explain-pgstat"), f);
var cols = document.getElementById("explain-cols");
cols.style.display = hasFix || hasPgstat ? "" : "none";
cols.classList.toggle("single", hasFix !== hasPgstat);
renderDetailFoot(document.getElementById("explain-foot"), f);
}
function renderPgStatXref(host, f) {
host.textContent = "";
var tpl = f.pattern && f.pattern.template;
var entry = tpl ? pgStatByTemplate[tpl] : null;
if (!entry) { host.style.display = "none"; return false; }
host.style.display = "";
host.appendChild(el("span", "ps-eyebrow", "// pg_stat"));
var row = el("div", "ps-pgstat-xref");
var code = el("code", null);
highlightInto(code, entry.normalized_template || tpl);
row.appendChild(code);
var occ = (f.pattern && f.pattern.occurrences) || 1;
var mean = Number.isFinite(entry.mean_exec_time_ms) ? fmtNum(entry.mean_exec_time_ms.toFixed(1)) + "ms" : "";
row.appendChild(el("span", "ps-pgstat-xref-stat", "×" + occ + " " + mean));
host.appendChild(row);
return true;
}
function extraOpsOf(f) {
var gi = f.green_impact;
return gi && Number.isFinite(gi.estimated_extra_io_ops) ? gi.estimated_extra_io_ops : 0;
}
var recurrenceIndex = null;
function recurrenceKey(f) {
return f.signature ||
[f.type || f.finding_type, f.service, f.source_endpoint,
(f.pattern && f.pattern.template) || ""].join("|");
}
function recurrenceOf(f) {
if (!recurrenceIndex) {
recurrenceIndex = Object.create(null);
findings.forEach(function (other) {
var key = recurrenceKey(other);
var acc = recurrenceIndex[key];
if (!acc) {
recurrenceIndex[key] = { count: 1, first: other.first_timestamp || "", last: other.last_timestamp || "", ops: extraOpsOf(other) };
return;
}
acc.count += 1;
acc.ops += extraOpsOf(other);
if (other.first_timestamp && (!acc.first || other.first_timestamp < acc.first)) acc.first = other.first_timestamp;
if (other.last_timestamp && (!acc.last || other.last_timestamp > acc.last)) acc.last = other.last_timestamp;
});
}
return recurrenceIndex[recurrenceKey(f)] || { count: 1, first: f.first_timestamp, last: f.last_timestamp };
}
function renderDetailFoot(host, f) {
host.textContent = "";
var parts = [];
var cl = f.code_location;
if (cl && cl.filepath) {
parts.push("source " + cl.filepath + (Number.isFinite(cl.lineno) ? ":" + cl.lineno : ""));
}
var rec = recurrenceOf(f);
if (rec.count > 1) parts.push("detected in " + rec.count + " traces");
var sameDay = rec.first && rec.last && rec.first.slice(0, 10) === rec.last.slice(0, 10);
if (rec.first) parts.push("seen first " + clockTime(rec.first, !sameDay));
if (rec.last) parts.push("last " + clockTime(rec.last, !sameDay));
if (parts.length === 0) { host.style.display = "none"; return; }
host.style.display = "";
host.textContent = parts.join(" · ");
}
function clockTime(iso, withDate) {
if (typeof iso !== "string" || iso.length < 19) return iso || "";
var time = iso.slice(11, 19) + " UTC";
return withDate ? iso.slice(0, 10) + " " + time : time;
}
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 collapseKey(span) {
var culprit = treeCulprits ? (treeCulprits[span.span_id] ? "1" : "0") : "";
return (span.event_type || "") + "|" + (span.template || span.target || "") + "|" + culprit;
}
function isLeafIdx(idx, spans, tree) {
var kids = tree.childrenByParent[spans[idx].span_id];
return !(kids && kids.length);
}
function groupSiblings(indices, spans, tree) {
var groups = [];
var i = 0;
while (i < indices.length) {
var idx = indices[i];
if (isLeafIdx(idx, spans, tree)) {
var key = collapseKey(spans[idx]);
var members = [idx];
var j = i + 1;
while (j < indices.length && isLeafIdx(indices[j], spans, tree)
&& collapseKey(spans[indices[j]]) === key) {
members.push(indices[j]);
j += 1;
}
groups.push(members);
i = j;
} else {
groups.push([idx]);
i += 1;
}
}
return groups;
}
function buildCollapsedRow(members, spans, depth, targetTpl) {
var row = buildSpanRow(spans[members[0]], depth, targetTpl);
var durEl = row.querySelector(".ps-span-dur");
row.insertBefore(el("span", "ps-span-count", "×" + members.length), durEl);
var totalUs = members.reduce(function (s, i) { return s + (spans[i].duration_us || 0); }, 0);
if (durEl) durEl.textContent = members.length + " × " + formatDurationUs(totalUs / members.length);
return row;
}
function emitSpanTreeRows(root, spans, tree, targetTpl) {
var stack = [];
function pushGroups(indices, depth) {
var groups = groupSiblings(indices, spans, tree);
for (var g = groups.length - 1; g >= 0; g--) {
stack.push({ group: groups[g], depth: depth });
}
}
pushGroups(tree.roots, 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;
if (frame.group.length > 1) {
root.appendChild(buildCollapsedRow(frame.group, spans, frame.depth, targetTpl));
emitted += 1;
continue;
}
var span = spans[frame.group[0]];
root.appendChild(buildSpanRow(span, frame.depth, targetTpl));
emitted += 1;
var kids = tree.childrenByParent[span.span_id] || [];
if (kids.length) pushGroups(kids, frame.depth + 1);
}
}
var WHOLE_TRACE_HILITE = {
chatty_service: "http_out",
pool_saturation: "sql"
};
var treeHasTplMatch = false;
var treeTargetEvt = null;
var treeChildren = {};
var treeCulprits = null;
function culpritSetFor(trace, finding) {
if (!finding.signature) return null;
var key = trace.trace_id + "|" + finding.signature + "|" +
(finding.first_timestamp || "") + "|" + (finding.last_timestamp || "");
var ids = culpritSpans[key];
if (!ids || !ids.length) return null;
var set = Object.create(null);
ids.forEach(function (id) { set[id] = true; });
return set;
}
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;
treeCulprits = culpritSetFor(trace, finding);
treeHasTplMatch = !treeCulprits && !!targetTpl
&& spans.some(function (s) { return s.template === targetTpl; });
treeTargetEvt = (treeCulprits || treeHasTplMatch) ? null
: (WHOLE_TRACE_HILITE[finding.type || finding.finding_type] || null);
treeChildren = tree.childrenByParent || {};
var host = root.closest ? root.closest(".ps-tree") : null;
setAttr(host || root, "data-fsev", finding.severity || "");
emitSpanTreeRows(root, spans, tree, targetTpl);
if (!root.querySelector(".ps-span.hilite")) {
root.querySelectorAll(".ps-span.dim").forEach(function (row) {
row.classList.remove("dim");
});
}
}
function buildSpanRow(span, depth, targetTpl) {
var row = el("div", "ps-span");
var isFinding = treeCulprits
? !!treeCulprits[span.span_id]
: treeHasTplMatch
? (targetTpl && span.template === targetTpl)
: (treeTargetEvt && span.event_type === treeTargetEvt &&
!(treeChildren[span.span_id] || []).length);
if (isFinding) row.classList.add("hilite");
else if (treeCulprits || treeHasTplMatch || treeTargetEvt) row.classList.add("dim");
row.style.paddingLeft = (10 + 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 () {
clearSearchQuery();
switchTab("pgstat");
showPgStatDrill(pgStatMatch);
});
}
var text = el("span", "ps-span-text");
if (isFinding) text.classList.add("ps-span-find");
var described = describeSpan(span);
highlightInto(text, described);
text.title = described;
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") {
if (span.template) return span.template;
return (span.operation || "GET").toUpperCase() + " " + (span.target || "");
}
return span.template || span.operation || span.target || "(span)";
}
function frameworkLabel(fw) {
if (!fw) return "-";
var s = fw.replace(/_generic$/, "").replace(/_/g, " ");
return s.replace(/^node\b/, "node.js").replace(/^csharp\b/, "c#");
}
function renderSuggestedFix(root, fix) {
root.appendChild(el("span", "ps-eyebrow brand", "// suggested fix · " + frameworkLabel(fix.framework)));
var prose = el("span", "ps-fix-prose");
proseInto(prose, fix.recommendation || "");
root.appendChild(prose);
var href = safeHttpsHref(fix.reference_url);
if (href) {
var a = el("a", "ps-fix-link", "See documentation ↗");
setAttr(a, "href", href);
setAttr(a, "target", "_blank");
setAttr(a, "rel", "noopener noreferrer");
root.appendChild(a);
}
}
function greenCard(label, value, sub, tone, help, deepKey) {
var card = metricCard(label, value, sub);
if (sub && sub.indexOf("`") !== -1) {
var subEl = card.querySelector(".ps-metric-sub");
subEl.textContent = "";
proseInto(subEl, sub);
}
var v = card.querySelector(".ps-metric-value");
if (v) setAttr(v, "data-tone", tone || "brand");
setAttr(card, "data-grad", tone || "brand");
if (help) {
var icon = el("span", "ps-nav-help", "?");
setAttr(icon, "aria-label", help);
wireTip(icon, help, deepKey);
card.querySelector(".ps-metric-label").appendChild(icon);
}
return card;
}
function bandTone(band) {
if (band === "critical") return "crit";
if (band === "high") return "warn";
if (band === "moderate") return "neutral";
if (band === "healthy") return "ok";
return "brand";
}
function renderCarbonFormula() {
var host = document.getElementById("green-formula");
if (!host) return;
host.textContent = "";
host.appendChild(el("code", "ps-formula-code", "co2.total = (E × I) + M"));
var text = el("span", "ps-formula-text");
var href = safeHttpsHref("https://greensoftware.foundation/");
if (href) {
var a = el("a", "ps-source-link", "SCI v1.0");
setAttr(a, "href", href);
setAttr(a, "target", "_blank");
setAttr(a, "rel", "noopener noreferrer");
text.appendChild(a);
} else {
text.appendChild(document.createTextNode("SCI v1.0"));
}
proseInto(
text,
" numerator (ISO/IEC 21031:2024): energy used (E) × grid carbon intensity (I), plus embodied hardware (M), summed over analyzed traces. Per-region scoring when spans carry `cloud.region`."
);
host.appendChild(text);
host.style.display = "";
}
function renderGreenPanel() {
if (!hasGreen) {
document.getElementById("green-metrics").appendChild(greenCard(
"Carbon",
"-",
(report.analysis && report.analysis.traces_analyzed === 0)
? "No traces were analyzed in this run, so there is no operational, embodied or transport figure to show."
: "GreenOps scoring is off ([green] enabled = false): no operational, embodied or transport figure for this run.",
"muted"
));
return;
}
var co2 = greenSummary.co2;
var metrics = document.getElementById("green-metrics");
metrics.appendChild(greenCard(
"Total CO2",
formatGco2(co2.total.mid),
"low " + formatGco2(co2.total.low) + " · high " + formatGco2(co2.total.high),
null,
"The SCI v1.0 numerator summed over analyzed traces: operational `(E x I)` + embodied `M` + network transport `T`. The low/high bracket is log-symmetric (half, double): a directional estimate, not a wattmeter reading.",
"total_co2"
));
metrics.appendChild(greenCard("Operational CO2", formatGco2(co2.operational_gco2), "E × I", null,
"Carbon from actually running the workload: energy used times the grid carbon intensity of the resolved region. The only term a code fix can reduce.", "operational_co2"));
metrics.appendChild(greenCard("Embodied CO2", formatGco2(co2.embodied_gco2), "SCI v1.0 · M term", null,
"Hardware manufacturing carbon amortized over traffic: `[green] embodied_carbon_per_request_gco2` per request. Real, but irreducible by fixing an anti-pattern.", "embodied_co2"));
var avoidMid = co2.avoidable.mid;
metrics.appendChild(greenCard(
"Avoidable",
formatGco2(avoidMid),
percent(avoidMid, co2.operational_gco2) + " of operational",
avoidMid > 0 ? "accent" : "neutral",
"The operational share attributable to avoidable I/O: operational carbon times avoidable ops over accounted ops. This is the number an anti-pattern fix shrinks.",
"avoidable_co2"
));
metrics.appendChild(greenCard(
"I/O waste ratio",
Math.round((greenSummary.io_waste_ratio || 0) * 100) + "%",
(greenSummary.total_sql_io_ops || 0) > 0
? "avoidable / total ops · SQL " + fmtNum(greenSummary.avoidable_sql_io_ops || 0) + " of " + fmtNum(greenSummary.total_sql_io_ops)
: "avoidable / total ops",
bandTone(greenSummary.io_waste_ratio_band),
"Avoidable I/O operations (N+1, redundant calls) over all I/O operations of the window. Bands: healthy under 10%, moderate to 30%, high to 50%, critical above.",
"waste_ratio"
));
var topOff = greenSummary.top_offenders && greenSummary.top_offenders[0];
metrics.appendChild(greenCard(
"IIS score",
topOff && Number.isFinite(topOff.io_intensity_score) ? topOff.io_intensity_score.toFixed(1) : "-",
"I/O intensity per call",
bandTone(topOff && topOff.io_intensity_band),
"I/O Intensity Score of the worst endpoint: its I/O operations (SQL, outbound HTTP, publishes) per call. Bands at 2, 5 and 10.",
"iis"
));
var dbw = greenSummary.database_waste;
if (dbw) {
var dbValue = Number.isFinite(dbw.waste_gco2) ? formatGco2(dbw.waste_gco2) : formatKwh(dbw.waste_kwh);
var dbScope = dbw.model === "estimated" ? "within the report totals" : "excluded from totals";
var dbSub = formatKwh(dbw.waste_kwh) + " of " + formatKwh(dbw.energy_kwh) + " · " +
Math.round((dbw.sql_waste_ratio || 0) * 100) + "% SQL · " +
(dbw.model || "-") + (dbw.region ? " · " + dbw.region : "") +
" · " + dbScope +
(dbw.model === "estimated" ? " · measured needs `[green.alumet.database]`" : "");
metrics.appendChild(greenCard(
"Database waste",
dbValue,
dbSub,
dbw.waste_kwh > 0 ? "accent" : "neutral",
"Avoidable database-side energy: measured on the declared database cgroup (Alumet) or estimated from the modeled SQL energy times the SQL waste ratio, the model tag says which. Estimated re-presents a share of the report totals, measured is an additional, separate scope.",
"db_waste"
));
} else {
metrics.appendChild(greenCard(
"Database waste",
"-",
"not measured · no SQL activity, a measured reading needs `[green.alumet.database]`",
"muted",
"Avoidable database-side energy: measured on the declared database cgroup (Alumet) or estimated from the modeled SQL energy times the SQL waste ratio, the model tag says which. Estimated re-presents a share of the report totals, measured is an additional, separate scope.",
"db_waste"
));
}
var mw = greenSummary.messaging_waste;
if (mw) {
var mwValue = Number.isFinite(mw.waste_gco2) ? formatGco2(mw.waste_gco2) : formatKwh(mw.waste_kwh);
var mwScope = mw.model === "estimated" ? "within the report totals" : "excluded from totals";
var mwSub = formatKwh(mw.waste_kwh) + " of " + formatKwh(mw.energy_kwh) + " · " +
Math.round((mw.messaging_waste_ratio || 0) * 100) + "% messaging · " +
(mw.model || "-") + (mw.region ? " · " + mw.region : "") +
" · " + mwScope +
(mw.model === "estimated" ? " · measured needs `[green.alumet.broker]`" : "");
metrics.appendChild(greenCard(
"Broker waste",
mwValue,
mwSub,
mw.waste_kwh > 0 ? "accent" : "neutral",
"Same as database waste, for the message broker: measured cgroup (Alumet), declared cluster (SPECpower) or estimated from the modeled publish energy. Kept out of every total.",
"broker_waste"
));
} else {
metrics.appendChild(greenCard(
"Broker waste",
"-",
"not measured · no broker publish spans, or no broker energy backend",
"muted",
"Same as database waste, for the message broker: measured cgroup (Alumet), declared cluster (SPECpower) or estimated from the modeled publish energy. Kept out of every total.",
"broker_waste"
));
}
renderCarbonFormula();
renderScoringConfigBandeau(greenSummary.scoring_config);
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-card").style.display = "";
var tbody = document.getElementById("green-regions-body");
regions.forEach(function (r) {
var tr = document.createElement("tr");
tr.appendChild(el("td", null, regionLabel(r.region)));
tr.appendChild(el("td", null, fmtNum((r.grid_intensity_gco2_kwh || 0).toFixed(0))));
tr.appendChild(el("td", null, fmtNum(r.io_ops || 0)));
tr.appendChild(el("td", null, formatGco2(r.co2_gco2 || 0)));
tr.appendChild(buildSourceCell(r));
tr.appendChild(buildEstimatedCell(r));
tbody.appendChild(tr);
});
}
function renderScoringConfigBandeau(cfg) {
var bandeau = document.getElementById("green-scoring-config");
if (!bandeau) return;
if (!cfg || cfg.electricity_maps === false) {
bandeau.style.display = "none";
return;
}
bandeau.replaceChildren(
el("span", "ps-scoring-label", "Carbon scoring:"),
buildApiVersionChip(cfg.api_version),
buildEmissionFactorChip(cfg.emission_factor_type),
buildTemporalGranularityChip(cfg.temporal_granularity)
);
bandeau.style.display = "";
}
function buildScoringChip(label, modifier, tooltip) {
var chip = el("span", "ps-scoring-chip ps-scoring-chip-" + modifier, label);
chip.setAttribute("title", tooltip);
return chip;
}
function buildApiVersionChip(version) {
if (version === "v3") {
return buildScoringChip(
"Electricity Maps v3",
"warning",
"v3 is in legacy mode. Migrate to v4 to silence the deprecation warning."
);
}
if (version === "v4") {
return buildScoringChip(
"Electricity Maps v4",
"neutral",
"API v4 is the current default."
);
}
if (version === "custom") {
return buildScoringChip(
"Electricity Maps custom",
"neutral",
"Custom endpoint without /vN suffix (proxy or mock)."
);
}
return buildScoringChip(
"Electricity Maps " + String(version),
"neutral",
"Unrecognized API version, displayed verbatim."
);
}
function buildEmissionFactorChip(factor) {
if (factor === "direct") {
return buildScoringChip(
"direct",
"accent",
"Direct emissions only. Used by some Scope 2 frameworks."
);
}
if (factor === "lifecycle") {
return buildScoringChip(
"lifecycle",
"neutral",
"Default. Includes upstream emissions (manufacturing, transport)."
);
}
return buildScoringChip(
String(factor),
"neutral",
"Unrecognized emission factor type, displayed verbatim."
);
}
function buildTemporalGranularityChip(granularity) {
if (granularity === "5_minutes" || granularity === "15_minutes") {
return buildScoringChip(
granularity,
"accent",
"Sub-hour granularity. Requires a paid Electricity Maps plan."
);
}
if (granularity === "hourly") {
return buildScoringChip(
"hourly",
"neutral",
"Default. Hourly average carbon intensity."
);
}
return buildScoringChip(
String(granularity),
"neutral",
"Unrecognized temporal granularity, displayed verbatim."
);
}
var REGION_LOCATIONS = {
"ap-northeast-1": "Japan", "ap-south-1": "India", "ap-southeast-1": "Singapore",
"ap-southeast-2": "Australia", "ca-central-1": "Canada", "eu-central-1": "Germany",
"eu-north-1": "Sweden", "eu-south-1": "Italy", "eu-west-1": "Ireland",
"eu-west-2": "United Kingdom", "eu-west-3": "France", "eu-west-4": "Netherlands",
"sa-east-1": "Brazil", "us-east-1": "Virginia", "us-east-2": "Ohio",
"us-west-1": "California", "us-west-2": "Oregon"
};
function regionLabel(code) {
var loc = code && Object.prototype.hasOwnProperty.call(REGION_LOCATIONS, code)
? REGION_LOCATIONS[code] : null;
return loc ? code + " (" + loc + ")" : (code || "-");
}
function buildSourceCell(r) {
var td = document.createElement("td");
if (r.intensity_source === "real_time") {
td.appendChild(document.createTextNode("real time ("));
var href = safeHttpsHref("https://app.electricitymaps.com/map/live/fifteen_minutes");
if (href) {
var a = el("a", "ps-source-link", "Electricity Maps");
setAttr(a, "href", href);
setAttr(a, "target", "_blank");
setAttr(a, "rel", "noopener noreferrer");
td.appendChild(a);
} else {
td.appendChild(document.createTextNode("Electricity Maps"));
}
td.appendChild(document.createTextNode(")"));
} else {
td.textContent = r.intensity_source ? r.intensity_source.replace(/_/g, " ") : "-";
}
return td;
}
function buildEstimatedCell(r) {
var td = document.createElement("td");
if (r.intensity_estimated === true) {
var badge = el("span", "ps-badge-estimated", "Estimated");
var tip = r.intensity_estimation_method
? "Method: " + r.intensity_estimation_method +
". Estimated by Electricity Maps using their internal model. " +
"Less precise than measured data but reliable for trend analysis."
: "Estimated by Electricity Maps. Less precise than measured data " +
"but reliable for trend analysis.";
badge.setAttribute("title", tip);
td.appendChild(badge);
} else if (r.intensity_estimated === false) {
var measured = el("span", "ps-badge-measured", "Measured");
measured.setAttribute("title", "Source data measured directly from the grid operator.");
td.appendChild(measured);
} else {
td.textContent = "-";
}
return td;
}
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 mysqlStat = payload.mysql_stat || null;
var mysqlStatRankings =
mysqlStat && Array.isArray(mysqlStat.rankings) ? mysqlStat.rankings : [];
var hasMysqlStat = mysqlStatRankings.some(function (r) {
return (r.entries || []).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 setRankByVisible(labelId, visible) {
var label = document.getElementById(labelId);
if (label) label.style.display = visible ? "" : "none";
}
function renderPgStatRankings() {
var root = document.getElementById("pgstat-rankings");
root.textContent = "";
if (pgStatRankings.length <= 1) {
root.style.display = "none";
setRankByVisible("pgstat-rankby-label", false);
return;
}
root.style.display = "";
setRankByVisible("pgstat-rankby-label", true);
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();
}
}
reapplySearchForTab("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);
var tplTd = el("td", "ps-pg-template");
highlightInto(tplTd, entry.normalized_template);
tr.appendChild(tplTd);
tr.appendChild(el("td", null, fmtNum(entry.calls || 0)));
tr.appendChild(el("td", null, fmtNum((entry.total_exec_time_ms || 0).toFixed(1))));
tr.appendChild(el("td", null, fmtNum((entry.mean_exec_time_ms || 0).toFixed(2))));
body.appendChild(tr);
});
refreshTableSort();
}
function rankingIndexWithTemplate(template) {
for (var i = 0; i < pgStatRankings.length; i++) {
var entries = pgStatRankings[i].entries || [];
var hit = entries.some(function (e) {
return e.normalized_template === template;
});
if (hit) return i;
}
return -1;
}
function showPgStatDrill(template) {
var inActive = activePgStatEntries().some(function (e) {
return e.normalized_template === template;
});
if (!inActive) {
var idx = rankingIndexWithTemplate(template);
if (idx >= 0) selectPgStatRanking(idx);
}
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" });
}
var activeMysqlRankingIndex = 0;
var MYSQLSTAT_LABELS = {
"top by total_exec_time": "Total time",
"top by calls": "Calls",
"top by mean_exec_time": "Mean time",
"top by rows_examined": "Rows examined"
};
function activeMysqlStatEntries() {
if (mysqlStatRankings.length === 0) return [];
var idx = Math.min(activeMysqlRankingIndex, mysqlStatRankings.length - 1);
return mysqlStatRankings[idx].entries || [];
}
function renderMysqlStatPanel() {
if (!hasMysqlStat) {
document.getElementById("mysqlstat-empty").style.display = "";
return;
}
renderMysqlStatRankings();
renderMysqlStatTable();
}
function renderMysqlStatRankings() {
var root = document.getElementById("mysqlstat-rankings");
root.textContent = "";
if (mysqlStatRankings.length <= 1) {
root.style.display = "none";
setRankByVisible("mysqlstat-rankby-label", false);
return;
}
root.style.display = "";
setRankByVisible("mysqlstat-rankby-label", true);
setAttr(root, "role", "radiogroup");
setAttr(root, "aria-label", "mysql_stat ranking");
mysqlStatRankings.forEach(function (r, idx) {
var label = MYSQLSTAT_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 === activeMysqlRankingIndex;
setAttr(btn, "aria-checked", isActive ? "true" : "false");
if (isActive) btn.classList.add("active");
btn.addEventListener("click", function () {
selectMysqlStatRanking(idx);
});
root.appendChild(btn);
});
}
function selectMysqlStatRanking(idx) {
if (idx === activeMysqlRankingIndex) return;
activeMysqlRankingIndex = idx;
var chips = document.querySelectorAll("#mysqlstat-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");
}
renderMysqlStatTable();
reapplySearchForTab("mysqlstat");
}
function renderMysqlStatTable() {
var body = document.getElementById("mysqlstat-body");
body.textContent = "";
activeMysqlStatEntries().forEach(function (entry) {
var tr = document.createElement("tr");
setAttr(tr, "data-template", entry.normalized_template);
var tplTd = el("td", "ps-pg-template");
highlightInto(tplTd, entry.normalized_template);
tr.appendChild(tplTd);
tr.appendChild(el("td", null, entry.schema_name || ""));
tr.appendChild(el("td", null, fmtNum(entry.calls || 0)));
tr.appendChild(el("td", null, fmtNum((entry.total_exec_time_ms || 0).toFixed(1))));
tr.appendChild(el("td", null, fmtNum((entry.mean_exec_time_ms || 0).toFixed(2))));
tr.appendChild(el("td", null, fmtNum(entry.rows_examined || 0)));
body.appendChild(tr);
});
refreshTableSort();
}
function renderDiffPanel() {
if (!hasDiff) return;
var fresh = (diffData.new_findings || []).slice().sort(bySeverityDesc);
var resolved = (diffData.resolved_findings || []).slice().sort(bySeverityDesc);
renderDiffFindingsList("diff-new-list", fresh, true);
renderDiffFindingsList("diff-resolved-list", resolved, "resolved");
var section = document.querySelector(".ps-diff-section-new");
if (section) {
if (fresh.length) setAttr(section, "data-sev", fresh[0].severity);
else section.removeAttribute("data-sev");
}
renderDiffSevTable(diffData.severity_changes || []);
renderDiffEndpTable(diffData.endpoint_metric_deltas || []);
}
var DIFF_SECTIONS = [
{
listId: "diff-new-list", headerId: "diff-new-header",
emptyId: "diff-new-empty", label: "New findings", noun: "new findings"
},
{
listId: "diff-resolved-list", headerId: "diff-resolved-header",
emptyId: "diff-resolved-empty", label: "Resolved findings", noun: "resolved findings"
}
];
function renderDiffFindingsList(listId, items, kind) {
var list = document.getElementById(listId);
list.textContent = "";
items.forEach(function (f) {
list.appendChild(buildDiffFindingRow(f, kind));
});
syncDiffHeaders();
}
function countVisible(selector) {
var shown = 0;
document.querySelectorAll(selector).forEach(function (row) {
if (row.style.display !== "none") shown++;
});
return shown;
}
function syncEmptyState(emptyId, shown, noun) {
var box = document.getElementById(emptyId);
if (!box) return;
box.textContent = globalSearchTerm()
? "No " + noun + " match the current search."
: "No " + noun + " in this report.";
box.style.display = shown === 0 ? "" : "none";
}
function syncDiffHeaders() {
DIFF_SECTIONS.forEach(function (s) {
var shown = countVisible("#" + s.listId + " .ps-row");
var hdr = document.getElementById(s.headerId);
if (hdr) hdr.textContent = s.label + " (" + shown + ")";
syncEmptyState(s.emptyId, shown, s.noun);
});
}
function syncCorrelationsEmpty() {
syncEmptyState(
"correlations-empty",
countVisible("#correlations-list .ps-corr-card"),
"correlations"
);
}
function buildDiffFindingRow(f, kind) {
var row = el("div", "ps-row ps-diff-row");
if (kind === "resolved") {
var fixed = el("span", "ps-sev", "FIXED");
setAttr(fixed, "data-sev", "ok");
row.appendChild(fixed);
} else {
row.appendChild(sevPill(f.severity));
}
row.appendChild(el("span", "ps-diff-type", typeLabel(f.type || f.finding_type)));
var diffEp = el("span", "ps-diff-endpoint");
highlightEndpoint(diffEp, f.source_endpoint || "-");
row.appendChild(diffEp);
var diffExtra = (f.service || "") + " " + ((f.pattern && f.pattern.template) || "");
if (diffExtra.trim()) row.appendChild(el("span", "ps-sr-only", diffExtra));
if (kind === "resolved") {
row.addEventListener("click", function () {
showToast(
"",
"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 () {
openExternalFindingDetail(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 + ")";
hdr.classList.toggle("empty", items.length === 0);
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)));
var epTd = el("td");
highlightEndpoint(epTd, (f.service || "-") + " · " + (f.source_endpoint || "-"));
tr.appendChild(epTd);
var beforeTd = el("td");
beforeTd.appendChild(sevPill(c.before_severity));
tr.appendChild(beforeTd);
var afterTd = el("td");
afterTd.appendChild(sevPill(c.after_severity));
tr.appendChild(afterTd);
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 + ")";
hdr.classList.toggle("empty", items.length === 0);
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 || "-"));
var epTd = el("td");
highlightEndpoint(epTd, d.endpoint || "-");
tr.appendChild(epTd);
tr.appendChild(el("td", null, fmtNum(d.before_io_ops || 0)));
tr.appendChild(el("td", "ps-diff-after", fmtNum(d.after_io_ops || 0)));
var delta = d.delta || 0;
var deltaTd = el("td", null, (delta > 0 ? "+" : "") + fmtNum(delta));
if (delta > 0) setAttr(deltaTd, "data-tone", "crit");
else if (delta < 0) setAttr(deltaTd, "data-tone", "ok");
tr.appendChild(deltaTd);
body.appendChild(tr);
});
}
function correlationMatchBlob(c) {
var src = c.source || {};
var tgt = c.target || {};
return [typeLabel(src.finding_type || "cross-trace"), src.service, tgt.service].join(" ");
}
function endpointSeverity(endpoint) {
var real = findRealFindingForCorrelation(endpoint || {});
return real ? real.severity : null;
}
function correlationSeverity(c) {
var a = SEV_RANK[endpointSeverity(c.source)] ?? 3;
var b = SEV_RANK[endpointSeverity(c.target)] ?? 3;
var worst = Math.min(a, b);
return Object.keys(SEV_RANK).find(function (k) { return SEV_RANK[k] === worst; }) || null;
}
function corrTag(endpoint, sev) {
var tag = el("span", "ps-corr-tag", typeLabel((endpoint || {}).finding_type || "cross-trace").toUpperCase());
setAttr(tag, "data-sev", sev === "critical" ? "crit" : sev === "info" ? "info" : "warn");
return tag;
}
function renderCorrelationsPanel() {
if (!hasCorrelations) return;
var list = document.getElementById("correlations-list");
list.textContent = "";
var ordered = correlations
.map(function (c) { return { c: c, rank: SEV_RANK[correlationSeverity(c)] ?? 3 }; })
.sort(function (a, b) {
return a.rank !== b.rank ? a.rank - b.rank : (b.c.confidence || 0) - (a.c.confidence || 0);
})
.map(function (entry) { return entry.c; });
ordered.forEach(function (c) {
var src = c.source || {};
var tgt = c.target || {};
var card = el("div", "ps-corr-card ps-correlation-row");
var srcReal = findRealFindingForCorrelation(src);
var srcZone = el("span", "ps-corr-side");
srcZone.appendChild(corrTag(src, srcReal ? srcReal.severity : null));
srcZone.appendChild(el("span", "ps-corr-path", src.service || "?"));
if (srcReal) {
srcZone.classList.add("ps-corr-side-link");
setAttr(srcZone, "role", "button");
srcZone.addEventListener("click", function () { gotoFinding(srcReal); });
}
card.appendChild(srcZone);
card.appendChild(el("span", "ps-corr-path", "→"));
var tgtReal = findRealFindingForCorrelation(tgt);
var tgtZone = el("span", "ps-corr-side");
tgtZone.appendChild(corrTag(tgt, tgtReal ? tgtReal.severity : null));
tgtZone.appendChild(el("span", "ps-corr-path", tgt.service || "?"));
if (tgtReal || c.sample_trace_id) {
tgtZone.classList.add("ps-corr-side-link");
setAttr(tgtZone, "role", "button");
tgtZone.addEventListener("click", function () {
if (tgtReal) gotoFinding(tgtReal);
else openExternalFindingDetail(syntheticFindingFromCorrelation(c));
});
}
card.appendChild(tgtZone);
var conf = Math.round((c.confidence || 0) * 100);
var lag = (c.median_lag_ms || 0).toFixed(0);
var n = c.co_occurrence_count || 0;
card.appendChild(
el("span", "ps-corr-meta", "conf " + conf + "% · lag " + lag + " ms · " + n + " traces")
);
list.appendChild(card);
});
syncCorrelationsEmpty();
}
function findRealFindingForCorrelation(endpoint) {
var sameKind = findings.filter(function (f) {
return (f.type || f.finding_type) === endpoint.finding_type &&
f.service === endpoint.service;
});
var exact = sameKind.filter(function (f) {
return ((f.pattern && f.pattern.template) || "") === (endpoint.template || "");
});
if (exact.length) return exact[0];
return sameKind.length === 1 ? sameKind[0] : null;
}
function syntheticFindingFromCorrelation(c) {
var tgt = c.target || {};
return {
trace_id: c.sample_trace_id,
type: tgt.finding_type || "cross-trace",
severity: "warning",
service: tgt.service || "-",
source_endpoint: "-",
pattern: { template: tgt.template || "" }
};
}
function matchRowByText(row, q) {
return (row.textContent || "").toLowerCase().includes(q);
}
function findingMatchesSearch(f, term) {
return matchesSearch(findingMatchBlob(f), term);
}
var SEARCHABLE_TABS = {
findings: {
rowsSelector: "#findings-list .ps-row",
filtersData: true,
countMatches: function () { return applyFilters().length; }
},
pgstat: {
rowsSelector: "#pgstat-body tr"
},
mysqlstat: {
rowsSelector: "#mysqlstat-body tr"
},
diff: {
rowsSelector: "#diff-new-list .ps-row, #diff-resolved-list .ps-row",
afterFilter: syncDiffHeaders
},
correlations: {
rowsSelector: "#correlations-list .ps-corr-card",
afterFilter: syncCorrelationsEmpty
}
};
function globalSearchTerm() {
var ts = document.getElementById("topbar-search");
return ts ? (ts.value || "").toLowerCase() : "";
}
function initSearchInputs() {
var ts = document.getElementById("topbar-search");
if (!ts) return;
ts.addEventListener("input", function () {
onSearchQueryChanged();
});
}
function onSearchQueryChanged() {
resetShownCount();
renderFindingsList();
applyGlobalSearch("findings");
scheduleHashWrite();
}
function clearSearchQuery() {
var ts = document.getElementById("topbar-search");
if (!ts || !ts.value) return;
ts.value = "";
onSearchQueryChanged();
}
function unmarkSearchMatches(root) {
root.querySelectorAll("mark.ps-mark").forEach(function (m) {
var p = m.parentNode;
p.replaceChild(document.createTextNode(m.textContent), m);
p.normalize();
});
}
function markSearchMatches(root, q) {
var ql = q.toLowerCase();
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode: function (n) {
if (!n.nodeValue || n.nodeValue.toLowerCase().indexOf(ql) < 0) return NodeFilter.FILTER_REJECT;
var pe = n.parentElement;
if (!pe || pe.closest("button, input, th, mark")) return NodeFilter.FILTER_REJECT;
return NodeFilter.FILTER_ACCEPT;
}
});
var nodes = [];
while (walker.nextNode()) nodes.push(walker.currentNode);
nodes.forEach(function (n) {
var t = n.nodeValue;
if (t.toLowerCase().length !== t.length) return;
var pos = 0, idx = t.toLowerCase().indexOf(ql);
var frag = document.createDocumentFragment();
while (idx >= 0) {
frag.appendChild(document.createTextNode(t.slice(pos, idx)));
frag.appendChild(el("mark", "ps-mark", t.substr(idx, q.length)));
pos = idx + q.length;
idx = t.toLowerCase().indexOf(ql, pos);
}
frag.appendChild(document.createTextNode(t.slice(pos)));
n.parentNode.replaceChild(frag, n);
});
}
function applySearchFilter(cfg, q, mark, knownCount) {
var matches = 0;
document.querySelectorAll(cfg.rowsSelector).forEach(function (row) {
unmarkSearchMatches(row);
var visible = cfg.filtersData || !q || matchRowByText(row, q);
if (!cfg.filtersData) row.style.display = visible ? "" : "none";
if (!visible) return;
matches++;
if (mark && q.length >= 2) markSearchMatches(row, q);
});
if (cfg.afterFilter) cfg.afterFilter();
if (!cfg.countMatches) return matches;
return knownCount != null ? knownCount : cfg.countMatches();
}
function updateNavBadge(tabKey, matches) {
var btn = document.getElementById("tab-" + tabKey);
var badge = btn ? btn.querySelector(".ps-nav-badge") : null;
if (!badge) return;
if (matches != null) {
badge.textContent = String(matches);
return;
}
tabs.forEach(function (t) {
if (t.key !== tabKey || !t.countFn) return;
var c = t.countFn();
if (c !== null && c !== undefined) badge.textContent = String(c);
});
}
function reapplySearchForTab(tabKey, knownCount) {
var cfg = SEARCHABLE_TABS[tabKey];
if (!cfg) return;
var q = globalSearchTerm();
var matches = applySearchFilter(cfg, q, tabKey === currentTab, knownCount);
updateNavBadge(tabKey, q || cfg.countMatches ? matches : null);
}
function applyGlobalSearch(skipTab) {
Object.keys(SEARCHABLE_TABS).forEach(function (tabKey) {
if (tabKey !== skipTab) reapplySearchForTab(tabKey);
});
}
function focusSearch() {
var ts = document.getElementById("topbar-search");
if (!ts) return;
ts.focus();
ts.select();
}
function closeSearch(input) {
clearSearchQuery();
if (input) input.blur();
}
function renderTrimBanner() {
var t = payload.trimmed_traces;
var f = payload.trimmed_findings;
if (!t && !f) return;
var parts = [];
if (f) {
parts.push("Showing " + f.kept + " of " + f.total +
" findings (trimmed for file size, critical first; the JSON report keeps the full set).");
}
if (t) {
parts.push("Showing " + t.kept + " of " + t.total +
" traces (trimmed for file size). Rerun with --max-traces-embedded <higher> to see more.");
}
var b = document.getElementById("trim-banner");
b.style.display = "";
b.textContent = parts.join(" ");
}
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, true);
});
(function wireDensityToggle() {
var btn = document.getElementById("density-toggle");
if (!btn) return;
function syncDensityLabel(hovering) {
var mode = document.documentElement.getAttribute("data-density");
var shown = hovering ? (mode === "compact" ? "Comfort" : "Compact")
: (mode === "compact" ? "Compact" : "Comfort");
var span = document.getElementById("density-label");
if (span) span.textContent = shown;
btn.title = mode === "compact" ? "Switch to comfort density" : "Switch to compact density";
}
btn.addEventListener("mouseenter", function () { syncDensityLabel(true); });
btn.addEventListener("mouseleave", function () { syncDensityLabel(false); });
btn.addEventListener("click", function () {
var next = document.documentElement.getAttribute("data-density") === "compact" ? "comfort" : "compact";
document.documentElement.setAttribute("data-density", next);
try { localStorage.setItem("perf-sentinel:density", next); } catch (e) { }
syncDensityLabel(btn.matches(":hover"));
});
syncDensityLabel(false);
})();
var SORT_SEV_RANK = { CRIT: 3, WARN: 2, INFO: 1, OK: 0 };
var SORT_UNIT_SCALE = { ug: 1e-6, mg: 1e-3, g: 1, kg: 1e3, us: 1e-3, ms: 1, s: 1e3 };
var SORT_NUM_RE = /^([+-]?\d[\d, \s]*(?:\.\d+)?)\s*(%|×|[a-zµ]{1,2})?$/i;
function sortNumericValue(t) {
var m = SORT_NUM_RE.exec(t.trim());
if (!m) return NaN;
var n = parseFloat(m[1].replace(/[, \s]/g, ""));
if (isNaN(n)) return NaN;
var unit = m[2];
if (!unit || unit === "%" || unit === "×") return n;
var scale = SORT_UNIT_SCALE[unit.toLowerCase().replace("µ", "u")];
return scale === undefined ? NaN : n * scale;
}
var tableSortState = {};
try { tableSortState = JSON.parse(localStorage.getItem("perf-sentinel:tablesort") || "{}") || {}; } catch (e) { tableSortState = {}; }
Object.keys(tableSortState).forEach(function (k) {
var v = tableSortState[k];
var ok = Array.isArray(v) && v.every(function (s) {
return s && Number.isInteger(s.i) && (s.dir === "ascending" || s.dir === "descending");
});
if (!ok) delete tableSortState[k];
});
try {
var fsortMatch = (location.hash || "").match(/[&#]fsort=(severity|impact)\.(asc|desc)/);
if (fsortMatch) {
sortState.key = fsortMatch[1];
sortState.dir = fsortMatch[2];
}
var tsortMatch = (location.hash || "").match(/[&#]tsort=([^&]+)/);
if (tsortMatch) decodeURIComponent(tsortMatch[1]).split(",").forEach(function (part) {
var li = part.lastIndexOf(":");
if (li < 0) return;
var spec = part.slice(li + 1);
if (!/^\d+[ad](\.\d+[ad])*$/.test(spec)) return;
tableSortState[part.slice(0, li)] = spec.split(".").map(function (s) {
return { i: parseInt(s, 10), dir: /a$/.test(s) ? "ascending" : "descending" };
});
});
} catch (e) { }
function saveTableSort() {
try { localStorage.setItem("perf-sentinel:tablesort", JSON.stringify(tableSortState)); } catch (e) { }
}
function tableSortHashValue() {
var panel = document.getElementById("panel-" + currentTab);
if (!panel) return null;
var active = Array.prototype.slice.call(panel.querySelectorAll(".ps-table")).map(function (t) {
return t.id;
}).filter(function (k) {
return k && tableSortState[k] && tableSortState[k].length;
});
if (!active.length) return null;
return active.map(function (k) {
return k + ":" + tableSortState[k].map(function (s) {
return s.i + (s.dir === "ascending" ? "a" : "d");
}).join(".");
}).join(",");
}
function sortCellText(tr, i) {
var td = tr.children[i];
return td ? td.textContent.trim() : "";
}
function sortColKind(rows, i) {
var num = 0, sev = 0, tot = 0;
rows.forEach(function (r) {
var t = sortCellText(r, i);
if (!t) return;
tot++;
if (SORT_SEV_RANK[t.toUpperCase()] !== undefined) sev++;
else if (!isNaN(sortNumericValue(t))) num++;
});
if (!tot) return "text";
if (sev / tot > 0.7) return "sev";
if (num / tot > 0.7) return "num";
return "text";
}
function sortFirstDir(kind) { return kind === "text" ? "ascending" : "descending"; }
function sortCmp(kind, ta, tb) {
if (kind === "sev") return (SORT_SEV_RANK[ta.toUpperCase()] ?? -1) - (SORT_SEV_RANK[tb.toUpperCase()] ?? -1);
if (kind === "num") {
var na = sortNumericValue(ta);
if (isNaN(na)) na = -Infinity;
var nb = sortNumericValue(tb);
if (isNaN(nb)) nb = -Infinity;
return na - nb;
}
return ta.localeCompare(tb);
}
function syncSortHeaders(table) {
var keys = tableSortState[table.id] || [];
table.querySelectorAll("thead th").forEach(function (th, i) {
var ico = th.querySelector(".ps-sort-ico");
var pos = keys.findIndex(function (k) { return k.i === i; });
if (pos < 0) {
th.removeAttribute("aria-sort");
if (ico) ico.textContent = "↕";
return;
}
var arrow = keys[pos].dir === "ascending" ? "↑" : "↓";
if (ico) ico.textContent = keys.length > 1 ? arrow + (pos + 1) : arrow;
if (pos === 0) th.setAttribute("aria-sort", keys[pos].dir);
else th.removeAttribute("aria-sort");
});
}
function applyTableSort(table) {
var tbody = table.tBodies[0];
if (!tbody) return;
var rows = Array.prototype.slice.call(tbody.children);
rows.forEach(function (r, idx) { if (!r.dataset.origIdx) r.dataset.origIdx = String(idx); });
var keys = (tableSortState[table.id] || []).map(function (k) {
return { i: k.i, dir: k.dir, kind: sortColKind(rows, k.i) };
});
rows.sort(function (a, b) {
if (keys.length) {
var ha = a.classList.contains("hilite") ? 0 : 1;
var hb = b.classList.contains("hilite") ? 0 : 1;
if (ha !== hb) return ha - hb;
}
for (var k = 0; k < keys.length; k++) {
var c = sortCmp(keys[k].kind, sortCellText(a, keys[k].i), sortCellText(b, keys[k].i));
if (c) return keys[k].dir === "ascending" ? c : -c;
}
return (+a.dataset.origIdx) - (+b.dataset.origIdx);
});
rows.forEach(function (r) { tbody.appendChild(r); });
syncSortHeaders(table);
}
function onSortHeaderClick(table, i, ev) {
var tbody = table.tBodies[0];
if (!tbody) return;
var rows = Array.prototype.slice.call(tbody.children);
var kind = sortColKind(rows, i);
var keys = tableSortState[table.id] || [];
var pos = keys.findIndex(function (k) { return k.i === i; });
if (ev && ev.shiftKey && keys.length && pos !== 0) {
if (pos < 0) keys.push({ i: i, dir: sortFirstDir(kind) });
else if (keys[pos].dir === sortFirstDir(kind)) keys[pos].dir = sortFirstDir(kind) === "ascending" ? "descending" : "ascending";
else keys.splice(pos, 1);
} else {
var cur = pos === 0 && keys.length === 1 ? keys[0].dir : null;
var f = sortFirstDir(kind);
var next = cur === null ? f : (cur === f ? (f === "ascending" ? "descending" : "ascending") : null);
keys = next === null ? [] : [{ i: i, dir: next }];
}
tableSortState[table.id] = keys;
saveTableSort();
applyTableSort(table);
}
function wireSortableTable(table) {
if (table.dataset.sortWired) return;
table.dataset.sortWired = "1";
table.querySelectorAll("thead th").forEach(function (th, i) {
if (!th.textContent.trim()) return;
th.setAttribute("data-sortable", "");
th.setAttribute("title", "Sort by " + th.textContent.trim() + ", Shift+click adds it as a tie-breaker (secondary sort)");
var ico = el("span", "ps-sort-ico", "↕");
th.appendChild(ico);
th.tabIndex = 0;
th.addEventListener("click", function (ev) { onSortHeaderClick(table, i, ev); });
th.addEventListener("keydown", function (ev) {
if (ev.key === "Enter" || ev.key === " ") {
ev.preventDefault();
onSortHeaderClick(table, i, ev);
}
});
});
}
function refreshTableSort() {
document.querySelectorAll(".ps-table").forEach(function (t) {
if (!t.id) return;
wireSortableTable(t);
var keys = tableSortState[t.id];
if (!keys || !keys.length) return;
var tb = t.tBodies[0];
if (tb && tb.children.length && !tb.children[0].dataset.origIdx) applyTableSort(t);
});
}
function csvEscape(value) {
if (value === null || value === undefined) return "";
var s = String(value);
if (s.length > 0 && "=+-@\t".includes(s.charAt(0)) && !/^[+-]?\d*\.?\d+$/.test(s)) {
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) {
if (!SEARCHABLE_TABS || !SEARCHABLE_TABS[tabKey]) return "";
return globalSearchTerm();
}
function matchesSearch(haystack, term) {
if (!term) return true;
return String(haystack || "").toLowerCase().includes(term);
}
function exportFindingsCsv() {
var header = [
"severity", "type", "service", "endpoint", "trace_id", "template",
"occurrences", "first_timestamp", "last_timestamp",
"code_function", "code_filepath", "code_lineno",
"suggestion", "suggested_fix_framework", "suggested_fix_recommendation",
"confidence"
];
var rows = applyFilters().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 || "",
f.trace_id || "",
p.template || "",
p.occurrences ?? "",
f.first_timestamp || "",
f.last_timestamp || "",
loc.function || "",
loc.filepath || "",
loc.lineno ?? "",
f.suggestion || "",
fix.framework || "",
fix.recommendation || "",
f.confidence ?? ""
];
});
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) {
var blob = [
e.normalized_template,
fmtNum(e.calls || 0),
fmtNum((e.total_exec_time_ms || 0).toFixed(1)),
fmtNum((e.mean_exec_time_ms || 0).toFixed(2))
].join(" ");
return matchesSearch(blob, 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 exportMysqlStatCsv() {
var term = searchTermFor("mysqlstat");
var header = [
"template", "schema_name", "calls", "total_exec_time_ms",
"mean_exec_time_ms", "rows_sent", "rows_examined", "seen_in_traces"
];
var rows = activeMysqlStatEntries().filter(function (e) {
var blob = [
e.normalized_template,
e.schema_name || "",
fmtNum(e.calls || 0),
fmtNum((e.total_exec_time_ms || 0).toFixed(1)),
fmtNum((e.mean_exec_time_ms || 0).toFixed(2)),
fmtNum(e.rows_examined || 0)
].join(" ");
return matchesSearch(blob, term);
}).map(function (e) {
return [
e.normalized_template || "",
e.schema_name || "",
e.calls ?? "",
e.total_exec_time_ms ?? "",
e.mean_exec_time_ms ?? "",
e.rows_sent ?? "",
e.rows_examined ?? "",
e.seen_in_traces ? "true" : "false"
];
});
downloadCsv("mysqlstat", buildCsv(header, rows));
}
function findingMatchBlob(f) {
var type = f.type || f.finding_type;
return [
f.severity, type, typeLabel(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(findingMatchBlob(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(findingMatchBlob(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 || {};
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) {
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) {
return matchesSearch(correlationMatchBlob(c), 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);
return /^\d+$/.test(slug) ? Number(slug) : -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 };
var term = globalSearchTerm();
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;
var ts = document.getElementById("topbar-search");
if (ts) ts.value = state.search || "";
if (state.tab === "findings") {
filterState.severity = null;
filterState.service = null;
if (state.severity === "critical" || state.severity === "warning" || state.severity === "info") {
filterState.severity = state.severity;
}
if (state.service && findings.some(function (f) { return f.service === state.service; })) {
filterState.service = state.service;
}
syncFilterChips();
}
if (state.tab === "pgstat") {
var idx = rankingIndexForSlug(state.ranking || "");
if (idx >= 0 && idx < pgStatRankings.length) {
selectPgStatRanking(idx);
}
}
resetShownCount();
renderFindingsList();
switchTab(state.tab);
return true;
}
var CHEATSHEET_ROWS = [
["j", "Move finding selection down"],
["k", "Move finding selection up"],
["Enter", "Open the selected finding"],
["Esc", "Close help, close search, clear filters"],
["/", "Focus the search box (searches every tab)"],
["⌘K / Ctrl+K", "Focus the global search"],
["g o", "Go to Overview"],
["g f", "Go to Findings"],
["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 Carbon (if available)"],
["g a", "Go to Acknowledgments (live mode)"],
["Click header", "Sort a table by that column (cycles down, up, default)"],
["Shift+Click header", "Add a column as tie-breaker (secondary sort)"],
["?", "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;
}
var openDlg = document.querySelector("dialog[open]");
if (openDlg) {
openDlg.close();
ev.preventDefault();
return;
}
if (isSearchInputFocused()) {
closeSearch(document.activeElement);
ev.preventDefault();
return;
}
if (currentTab === "findings" && anyFilterChipActive()) {
clearFilterChips();
scheduleHashWrite();
ev.preventDefault();
}
}
function handleSlash(ev) {
if (isTextInputFocused()) return;
focusSearch();
ev.preventDefault();
}
function handleFindingsNav(ev) {
if (visibleFindings.length === 0) return;
if (ev.key === "j") {
selectFinding((selectedIndex + 1) % visibleFindings.length);
scrollSelectedIntoView();
} else if (ev.key === "k") {
selectFinding((selectedIndex - 1 + visibleFindings.length) % visibleFindings.length);
scrollSelectedIntoView();
} else if (ev.key === "Enter" && selectedIndex >= 0) {
selectFinding(selectedIndex);
}
ev.preventDefault();
}
var pendingGTimer = null;
var G_MAPPING = {
o: "overview", f: "findings", p: "pgstat",
d: "diff", c: "correlations", r: "green", a: "acknowledgments"
};
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.metaKey || ev.ctrlKey) && (ev.key === "k" || ev.key === "K")) {
var topbarSearch = document.getElementById("topbar-search");
if (topbarSearch) { topbarSearch.focus(); topbarSearch.select(); ev.preventDefault(); }
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" });
}
}
function scrollDetailToTop() {
var pane = document.getElementById("explain-content");
if (!pane) return;
var bar = document.querySelector(".ps-topbar");
var offset = (bar ? bar.offsetHeight : 0) + 8;
if (pane.getBoundingClientRect().top >= offset) return;
window.scrollTo({ top: 0, behavior: "smooth" });
}
document.getElementById("pgstat-drill-clear").addEventListener("click", clearPgStatDrill);
function registerAllTabs() {
registerTab("overview", "Overview", null);
registerTab("findings", "Findings", function () { return findings.length; });
if (hasPgStat) {
registerTab("pgstat", "pg_stat", function () {
return activePgStatEntries().length;
});
} else {
registerDisabledTab("pgstat", "pg_stat",
"No `pg_stat_statements` data in this report. Pass `--pg-stat <file>` or `--pg-stat-prometheus <url>` to `perf-sentinel report` to enable this tab.");
}
if (hasMysqlStat) {
registerTab("mysqlstat", "mysql_stat", function () {
return activeMysqlStatEntries().length;
});
} else {
registerDisabledTab("mysqlstat", "mysql_stat",
"No MySQL Performance Schema data in this report. Pass `--mysql-stat <file>` (an `events_statements_summary_by_digest` export) to `perf-sentinel report` to enable this tab.");
}
if (hasDiff) {
registerTab("diff", "Diff", function () {
return (
(diffData.new_findings || []).length + (diffData.resolved_findings || []).length
);
});
}
if (!hasDiff) {
registerDisabledTab("diff", "Diff",
"No baseline to compare against. Pass `--before <baseline.json>` (from `analyze --format json`) to `perf-sentinel report` to enable this tab.");
}
if (hasCorrelations) {
registerTab("correlations", "Correlations", function () {
return correlations.length;
});
} else {
var conf = findings.length ? findings[0].confidence : null;
var daemonRun = CONFIDENCE_CONTEXT[conf]
? (conf === "daemon_staging" || conf === "daemon_production")
: !!(payload.daemon && payload.daemon.url);
registerDisabledTab("correlations", "Correlations", daemonRun
? "The daemon reported no cross-trace correlation: `[daemon.correlation]` may be disabled in the config, or none was detected in the window."
: "Cross-trace correlations are a daemon-only output (`perf-sentinel watch`). This report came from a batch run, which analyzes each trace alone.");
}
registerTab("green", "Carbon", null);
if (payload.daemon && payload.daemon.url) {
registerTab("acknowledgments", "Acknowledgments", function () {
return liveAcksCount();
});
}
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();
renderOverviewPanel();
renderFilterChips();
restorePersistedPgStatRanking();
renderFindingsList();
if (hasPgStat) renderPgStatPanel();
if (hasMysqlStat) renderMysqlStatPanel();
if (hasDiff) renderDiffPanel();
if (hasCorrelations) renderCorrelationsPanel();
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();
});
var footerBtn = document.getElementById("footer-shortcuts");
if (footerBtn) footerBtn.addEventListener("click", openCheatsheet);
}
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,
mysqlstat: exportMysqlStatCsv,
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) {
var href = location.href;
var tsort = tableSortHashValue();
if (location.hash && tsort) {
href = href.replace(/([&#])tsort=[^&]*/g, "$1").replace(/[&#]$/, "");
href += "&tsort=" + encodeURIComponent(tsort);
}
if (location.hash) {
href = href.replace(/([&#])fsort=[^&]*/g, "$1").replace(/[&#]$/, "");
if (sortState.key !== "severity" || sortState.dir !== "desc") {
href += "&fsort=" + encodeURIComponent(sortState.key + "." + sortState.dir);
}
}
copyTextToClipboard(btn, href);
}
function copyTextToClipboard(btn, text) {
if (!btn.dataset.baseLabel) btn.dataset.baseLabel = btn.textContent;
var baseLabel = btn.dataset.baseLabel;
function flashCopied() {
btn.textContent = "Copied";
btn.classList.add("ps-copied");
if (btn._copyFlashTimer) clearTimeout(btn._copyFlashTimer);
btn._copyFlashTimer = setTimeout(function () {
btn._copyFlashTimer = null;
btn.textContent = baseLabel;
btn.classList.remove("ps-copied");
}, 1500);
}
function fallbackCopy() {
try {
var ta = document.createElement("textarea");
ta.value = text;
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("copyTextToClipboard: execCommand('copy') returned false");
}
} catch (err) {
console.error("copyTextToClipboard fallback failed", err);
}
}
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
navigator.clipboard.writeText(text).then(flashCopied, function (err) {
console.error("navigator.clipboard.writeText rejected", err);
fallbackCopy();
});
} else {
fallbackCopy();
}
}
function wireCopyLinkButtons() {
["findings", "pgstat", "mysqlstat", "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());
});
}
var DAEMON_API_KEY_STORAGE = "perf-sentinel.daemon.api-key";
var DAEMON_ACKS_CAP = 1000;
var DAEMON_FETCH_TIMEOUT_MS = 10000;
var liveState = {
enabled: false,
daemonUrl: null,
apiKey: null,
acks: [],
acksBySig: Object.create(null),
pendingAuthRetry: null,
activeFindingsAbort: null
};
function liveAcksCount() {
return liveState.acks.length;
}
function isLiveEnabled() {
return liveState.enabled;
}
function setApiKey(value) {
if (typeof value !== "string" || value.trim() === "") {
clearApiKey();
return;
}
liveState.apiKey = value;
sessionSet(DAEMON_API_KEY_STORAGE, value);
}
function clearApiKey() {
liveState.apiKey = null;
try {
globalThis.sessionStorage.removeItem(DAEMON_API_KEY_STORAGE);
} catch {
sessionSet(DAEMON_API_KEY_STORAGE, "");
}
}
function liveDaemonBase() {
return liveState.daemonUrl || "";
}
function fetchWithAuth(path, opts) {
var options = opts || {};
var headers = {};
if (options.headers) {
Object.keys(options.headers).forEach(function (k) {
headers[k] = options.headers[k];
});
}
headers["Accept"] = "application/json";
if (liveState.apiKey) {
headers["X-API-Key"] = liveState.apiKey;
}
var ctrl = (typeof AbortController === "function") ? new AbortController() : null;
var init = {
method: options.method || "GET",
headers: headers,
credentials: "omit",
mode: "cors"
};
if (options.body !== undefined && options.body !== null) {
init.body = options.body;
}
if (ctrl) {
init.signal = ctrl.signal;
} else if (options.signal) {
init.signal = options.signal;
}
var timer = ctrl ? setTimeout(function () { ctrl.abort(); }, DAEMON_FETCH_TIMEOUT_MS) : null;
return fetch(liveDaemonBase() + path, init).finally(function () {
if (timer) clearTimeout(timer);
});
}
function setDaemonStatus(state, label) {
var dot = document.getElementById("ps-daemon-dot");
var text = document.getElementById("ps-daemon-status-text");
if (dot) {
dot.classList.remove("connected", "disconnected", "unauthorized");
dot.classList.add(state);
}
if (text) text.textContent = label;
}
function showToast(kind, message) {
var existing = document.getElementById("ps-toast");
if (existing) existing.remove();
var t = el("div", "ps-toast " + (kind || ""), message);
setAttr(t, "id", "ps-toast");
setAttr(t, "role", kind === "error" ? "alert" : "status");
document.body.appendChild(t);
setTimeout(function () {
t.remove();
}, 5000);
}
function showUndoAckToast(sig) {
var existing = document.getElementById("ps-toast");
if (existing) existing.remove();
var t = el("div", "ps-toast success", "Acknowledged. ");
setAttr(t, "id", "ps-toast");
setAttr(t, "role", "status");
var undo = el("button", "ps-toast-undo", "Undo");
undo.type = "button";
function undoAck() {
var path = "/api/findings/" + encodeURIComponent(sig) + "/ack";
return fetchWithAuth(path, { method: "DELETE" }).then(function (r) {
if (r.status === 204) {
showToast("success", "Acknowledgment undone.");
return refreshLiveState();
}
return handleAuthRetry(r, undoAck, null, "revoke");
}).catch(function () {
showToast("error", "Network error reaching the daemon.");
});
}
undo.addEventListener("click", function () {
t.remove();
undoAck();
});
t.appendChild(undo);
document.body.appendChild(t);
setTimeout(function () { t.remove(); }, 8000);
}
function pingStatus() {
return fetchWithAuth("/api/status").then(function (r) {
if (r.status === 200) {
setDaemonStatus("connected", "Connected");
return true;
}
if (r.status === 401) {
if (liveState.apiKey) {
clearApiKey();
syncLogoutButtonVisibility();
}
setDaemonStatus("unauthorized", "Authentication required");
return false;
}
setDaemonStatus("disconnected", "HTTP " + r.status);
return false;
}).catch(function () {
setDaemonStatus("disconnected", "Unreachable");
return false;
});
}
function clearAcksState() {
liveState.acks = [];
liveState.acksBySig = Object.create(null);
}
function applyAcksPayload(data) {
var arr = Array.isArray(data) ? data : [];
liveState.acks = arr;
liveState.acksBySig = Object.create(null);
for (var a of arr) {
if (a && a.signature) liveState.acksBySig[a.signature] = a;
}
}
function handleAcksResponse(r) {
if (!r.ok) {
if (r.status === 401) {
setDaemonStatus("unauthorized", "Authentication required");
}
clearAcksState();
return null;
}
return r.json().then(applyAcksPayload);
}
function fetchAcks() {
return fetchWithAuth("/api/acks").then(handleAcksResponse).catch(clearAcksState);
}
function renderAcksPanel() {
var body = document.getElementById("acks-body");
var empty = document.getElementById("acks-empty");
var footer = document.getElementById("acks-footer");
var card = document.getElementById("acks-card");
if (!body) return;
body.textContent = "";
if (liveState.acks.length === 0) {
if (empty) empty.style.display = "";
if (card) card.style.display = "none";
if (footer) footer.textContent = "";
return;
}
if (empty) empty.style.display = "none";
if (card) card.style.display = "";
liveState.acks.forEach(function (a) {
body.appendChild(buildAckRow(a));
});
refreshTableSort();
if (footer) {
var note = liveState.acks.length + " active";
if (liveState.acks.length >= DAEMON_ACKS_CAP) {
note += " (showing up to " + DAEMON_ACKS_CAP + ", consult the daemon for the full list)";
}
note += ". TOML CI acks are not listed here, see .perf-sentinel-acknowledgments.toml.";
footer.textContent = note;
}
}
function buildAckRow(ack) {
var tr = document.createElement("tr");
var sig = document.createElement("td");
sig.textContent = ack.signature || "-";
tr.appendChild(sig);
var by = document.createElement("td");
by.textContent = ack.by || "-";
tr.appendChild(by);
var reason = document.createElement("td");
reason.textContent = ack.reason || "";
tr.appendChild(reason);
var expires = document.createElement("td");
expires.textContent = ack.expires_at ? ack.expires_at : "never";
tr.appendChild(expires);
var act = document.createElement("td");
var btn = el("button", "ps-fin-action-btn revoke", "Revoke");
btn.type = "button";
setAttr(btn, "data-sig", ack.signature || "");
btn.addEventListener("click", function () {
revokeAck(ack.signature);
});
act.appendChild(btn);
tr.appendChild(act);
return tr;
}
function syncFindingActionButtons() {
if (!isLiveEnabled()) return;
var rows = document.querySelectorAll("#findings-list .ps-row[data-sig]");
rows.forEach(function (row) {
var sig = row.dataset.sig || "";
if (!sig) return;
var btn = row.querySelector(".ps-fin-action-btn");
if (!btn) return;
var isAcked = !!liveState.acksBySig[sig];
btn.textContent = isAcked ? "Revoke" : "Ack";
setAttr(btn, "data-action", isAcked ? "revoke" : "ack");
btn.classList.toggle("revoke", isAcked);
btn.onclick = function (event) {
event.stopPropagation();
if (isAcked) revokeAck(sig);
else openAckModal(sig);
};
});
}
function openAckModal(sig) {
var dlg = document.getElementById("ack-modal");
var sigInput = document.getElementById("ack-modal-sig");
var reasonInput = document.getElementById("ack-modal-reason");
var byInput = document.getElementById("ack-modal-by");
var err = document.getElementById("ack-modal-error");
if (!dlg || !sigInput || !reasonInput) return;
sigInput.value = sig;
reasonInput.value = "";
if (byInput) byInput.value = "";
if (err) { err.style.display = "none"; err.textContent = ""; }
if (typeof dlg.showModal === "function") dlg.showModal();
}
function closeAckModal() {
var dlg = document.getElementById("ack-modal");
if (dlg && typeof dlg.close === "function") dlg.close();
}
function expiresIsoFromShortcut(value) {
if (!value) return null;
var now = new Date();
var hours = 0;
if (value === "24h") hours = 24;
else if (value === "7d") hours = 24 * 7;
else if (value === "30d") hours = 24 * 30;
else return null;
var dt = new Date(now.getTime() + hours * 3600 * 1000);
return dt.toISOString();
}
function submitAckModal(event) {
event.preventDefault();
var sig = (document.getElementById("ack-modal-sig") || {}).value || "";
var reason = ((document.getElementById("ack-modal-reason") || {}).value || "").trim();
var expiresValue = (document.getElementById("ack-modal-expires") || {}).value || "";
var by = ((document.getElementById("ack-modal-by") || {}).value || "").trim();
var err = document.getElementById("ack-modal-error");
if (!sig) return;
if (!reason) {
if (err) { err.textContent = "Reason is required."; err.style.display = "block"; }
return;
}
var body = { reason: reason };
if (by) body.by = by;
var expiresIso = expiresIsoFromShortcut(expiresValue);
if (expiresIso) body.expires_at = expiresIso;
postAck(sig, body, err);
}
function postAck(sig, body, errEl) {
var path = "/api/findings/" + encodeURIComponent(sig) + "/ack";
fetchWithAuth(path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
}).then(function (r) {
if (r.status === 201) {
closeAckModal();
showUndoAckToast(sig);
return refreshLiveState();
}
return handleAuthRetry(r, function () { postAck(sig, body, errEl); }, errEl, "create");
}).catch(function () {
if (errEl) { errEl.textContent = "Network error reaching the daemon."; errEl.style.display = "block"; }
});
}
function revokeAck(sig) {
if (!sig) return;
if (!globalThis.confirm || !globalThis.confirm("Revoke acknowledgment for " + sig + "?")) return;
var path = "/api/findings/" + encodeURIComponent(sig) + "/ack";
fetchWithAuth(path, { method: "DELETE" }).then(function (r) {
if (r.status === 204) {
showToast("success", "Acknowledgment revoked.");
return refreshLiveState();
}
return handleAuthRetry(r, function () { revokeAck(sig); }, null, "revoke");
}).catch(function () {
showToast("error", "Network error reaching the daemon.");
});
}
function handleAuthRetry(response, retry, errEl, opLabel) {
if (response.status === 401) {
liveState.pendingAuthRetry = retry;
var hadKey = liveState.apiKey !== null && liveState.apiKey !== "";
if (hadKey) {
clearApiKey();
syncLogoutButtonVisibility();
}
openAuthModal(hadKey ? "Invalid API key, please try again." : null);
return;
}
var msg = "HTTP " + response.status + " on " + opLabel;
if (errEl) { errEl.textContent = msg; errEl.style.display = "block"; }
else showToast("error", msg);
}
function openAuthModal(errorMessage) {
var dlg = document.getElementById("auth-modal");
var keyInput = document.getElementById("auth-modal-key");
var err = document.getElementById("auth-modal-error");
if (!dlg) return;
if (keyInput) keyInput.value = "";
if (err) {
if (errorMessage) {
err.textContent = errorMessage;
err.style.display = "block";
} else {
err.style.display = "none";
err.textContent = "";
}
}
if (typeof dlg.showModal === "function") dlg.showModal();
if (keyInput) {
setTimeout(function () { keyInput.focus(); }, 0);
}
}
function closeAuthModal() {
var dlg = document.getElementById("auth-modal");
if (dlg && typeof dlg.close === "function") dlg.close();
}
function submitAuthModal(event) {
event.preventDefault();
var keyInput = document.getElementById("auth-modal-key");
var err = document.getElementById("auth-modal-error");
var k = ((keyInput || {}).value || "").trim();
if (!k) {
if (err) { err.textContent = "API key is required."; err.style.display = "block"; }
return;
}
setApiKey(k);
syncLogoutButtonVisibility();
closeAuthModal();
var retry = liveState.pendingAuthRetry;
liveState.pendingAuthRetry = null;
pingStatus();
if (typeof retry === "function") retry();
}
function refreshLiveState() {
return Promise.all([pingStatus(), fetchAcks()]).then(function () {
renderAcksPanel();
renderFindingsList();
syncAcksTabBadge();
});
}
function syncAcksTabBadge() {
var btn = document.getElementById("tab-acknowledgments");
if (!btn) return;
var count = liveAcksCount();
if (count === null || count === undefined) return;
var badge = btn.querySelector(".ps-nav-badge");
if (badge) {
badge.textContent = String(count);
} else {
btn.appendChild(el("span", "ps-nav-badge", String(count)));
}
}
function wireRefreshButton() {
var btn = document.getElementById("ps-refresh-btn");
if (!btn) return;
btn.addEventListener("click", function () {
var ico = btn.querySelector(".ps-refresh-ico");
if (ico) {
btn.classList.add("ps-spinning");
ico.addEventListener("animationend", function () { btn.classList.remove("ps-spinning"); }, { once: true });
}
btn.disabled = true;
refreshLiveState().then(function () { btn.disabled = false; });
});
}
function syncLogoutButtonVisibility() {
var btn = document.getElementById("ps-logout-btn");
if (!btn) return;
if (liveState.apiKey) btn.hidden = false;
else btn.hidden = true;
}
function wireLogoutButton() {
var btn = document.getElementById("ps-logout-btn");
if (!btn) return;
btn.addEventListener("click", function () {
clearApiKey();
syncLogoutButtonVisibility();
showToast("success", "API key cleared from this tab.");
pingStatus();
});
}
function wireIncludeAckedToggle() {
var input = document.getElementById("findings-include-acked");
if (!input) return;
input.addEventListener("change", function () {
resetShownCount();
renderFindingsList();
});
}
function wireAuthModal() {
var form = document.getElementById("auth-modal-form");
var cancel = document.getElementById("auth-modal-cancel");
var close = document.getElementById("auth-modal-close");
if (form) form.addEventListener("submit", submitAuthModal);
if (cancel) cancel.addEventListener("click", function () {
closeAuthModal();
liveState.pendingAuthRetry = null;
setDaemonStatus("unauthorized", "Authentication required");
});
if (close) close.addEventListener("click", function () {
closeAuthModal();
liveState.pendingAuthRetry = null;
});
}
function wireAckModal() {
var form = document.getElementById("ack-modal-form");
var cancel = document.getElementById("ack-modal-cancel");
var close = document.getElementById("ack-modal-close");
if (form) form.addEventListener("submit", submitAckModal);
if (cancel) cancel.addEventListener("click", closeAckModal);
if (close) close.addEventListener("click", closeAckModal);
}
function stripTrailingSlashes(s) {
var end = s.length;
while (end > 0 && s.codePointAt(end - 1) === 47) end--;
return s.substring(0, end);
}
function bootLiveMode() {
if (!payload.daemon || !payload.daemon.url) return;
liveState.enabled = true;
liveState.daemonUrl = stripTrailingSlashes(String(payload.daemon.url));
var stored = sessionGet(DAEMON_API_KEY_STORAGE);
if (stored) liveState.apiKey = stored;
document.body.classList.add("ps-live");
wireRefreshButton();
wireLogoutButton();
wireIncludeAckedToggle();
wireAuthModal();
wireAckModal();
syncLogoutButtonVisibility();
setDaemonStatus("disconnected", "Connecting...");
refreshLiveState();
}
renderTopbar();
registerAllTabs();
renderAllPanels();
initSearchInputs();
populateCheatsheet();
wireCheatsheetDialog();
wireTopicHelpDialog();
wireShowMoreButton();
wireExportButtons();
wireCopyLinkButtons();
applyInitialHashOrWrite();
refreshTableSort();
wireHashChangeListener();
bootLiveMode();
(function adaptShortcutHint() {
var isMac = /Mac|iPhone|iPad/i.test((navigator.userAgentData && navigator.userAgentData.platform) || navigator.platform || "");
if (!isMac) {
var kbd = document.querySelector("#topbar-search-box .ps-search-kbd");
if (kbd) kbd.textContent = "Ctrl+K";
}
})();
})();
</script>
</body>
</html>