<!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); }
.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="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); }
#correlations-list { display: flex; flex-direction: column; gap: 10px; }
.ps-corr-card { display: flex; align-items: center; gap: 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 { 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-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 { 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-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-meta-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.ps-meta-cell { 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; }
.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; }
.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); 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; }
.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 { margin-top: auto; min-height: 62px; border-top: 1px solid var(--border); display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); }
.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-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-correlation-clickable { cursor: pointer; transition: box-shadow .12s; }
.ps-correlation-clickable:hover { background: var(--surface); 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 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 {
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 { min-height: 0; padding: 18px 0 14px; }
}
</style>
</head>
<body>
<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>
<h1 class="ps-topbar-h1" id="topbar-h1">Overview</h1>
</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>
<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 class="ps-detail-empty" id="explain-not-embedded" style="display: none;"></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">
<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">
<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>
<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>
</div>
</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="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);
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 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 TYPE_LABEL = {
n_plus_one_sql: "N+1 SQL",
n_plus_one_http: "N+1 HTTP",
redundant_sql: "Redundant SQL",
redundant_http: "Redundant HTTP",
slow_sql: "Slow SQL",
slow_http: "Slow HTTP",
excessive_fanout: "Excessive fanout",
chatty_service: "Chatty service",
pool_saturation: "Pool saturation",
serialized_calls: "Serialized calls"
};
function typeLabel(t) { return TYPE_LABEL[t] || String(t); }
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" }
};
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 tabs = [];
var registeredTabs = Object.create(null);
function registerTab(key, label, countFn) {
tabs.push({ key: key, label: label, countFn: countFn });
registeredTabs[key] = true;
}
function tabIsRegistered(key) { return !!registeredTabs[key]; }
function renderTabs() {
var bar = document.getElementById("tabs");
tabs.forEach(function (t) {
var btn = el("button", "ps-tab");
btn.type = "button";
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");
btn.appendChild(el("span", "ps-nav-dot"));
btn.appendChild(el("span", "ps-nav-label", t.label));
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(".ps-tab").forEach(function (btn) {
var active = btn.dataset.tab === key;
btn.classList.toggle("active", active);
setAttr(btn, "aria-selected", active ? "true" : "false");
setAttr(btn, "tabindex", active ? "0" : "-1");
});
tabs.forEach(function (t) {
var panel = document.getElementById("panel-" + t.key);
if (panel) panel.style.display = t.key === key ? "" : "none";
});
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+)",
io_waste_ratio_max: "Max I/O waste ratio"
};
function humanizeGateRule(key) {
if (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);
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 = (greenSummary && greenSummary.total_io_ops) || 0;
var list = el("div", "ps-list");
var SEV_RANK = { critical: 0, warning: 1, info: 2 };
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);
}
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 };
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");
});
syncFilterChips();
}
function onFilterClick(key) {
if (key === "all") {
filterState.severity = null;
filterState.service = null;
} else if (key.startsWith("sev:")) {
var sv = key.slice(4);
filterState.severity = filterState.severity === sv ? null : sv;
} else if (key.startsWith("svc:")) {
var svc = key.slice(4);
filterState.service = filterState.service === svc ? null : svc;
}
syncFilterChips();
resetShownCount();
renderFindingsList();
scheduleHashWrite();
}
function syncFilterChips() {
var anyActive = !!(filterState.severity || filterState.service);
document.querySelectorAll("#findings-filters .ps-chip").forEach(function (chip) {
var key = chip.dataset.key;
var active = false;
if (key === "all") active = !anyActive;
else if (key === "sev:" + filterState.severity) active = true;
else if (key === "svc:" + filterState.service) active = true;
chip.classList.toggle("active", active);
var role = chip.getAttribute("role");
var stateAttr = role === "radio" ? "aria-checked" : "aria-pressed";
setAttr(chip, stateAttr, active ? "true" : "false");
});
}
var LIST_CAP = 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);
});
}
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;
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);
});
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 "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 "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 ioImpactText(f) {
var gi = f.green_impact;
if (!gi) return "-";
var totalIo = (greenSummary && greenSummary.total_io_ops) || 0;
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 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");
titleWrap.appendChild(el("h2", "ps-detail-h2", typeLabel(f.type || f.finding_type)));
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);
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);
sec.appendChild(code);
head.appendChild(sec);
}
}
function openExplain(f) {
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];
if (!trace) {
content.style.display = "none";
notEmbedded.style.display = "";
notEmbedded.textContent =
"Trace not embedded (cap reached). Rerun with --max-traces-embedded <higher> to include it.";
return;
}
content.style.display = "";
var breadcrumb = document.getElementById("explain-breadcrumb");
var shortTraceId = (f.trace_id || "").slice(0, 12);
breadcrumb.textContent = "";
highlightEndpoint(
breadcrumb,
"trace_id = " + shortTraceId + " · " + (f.service || "-") + " · " + (f.source_endpoint || "-")
);
var treeRoot = document.getElementById("explain-tree");
treeRoot.textContent = "";
renderTree(treeRoot, trace, f);
var fix = document.getElementById("explain-fix");
fix.textContent = "";
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 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 : ""));
}
if (f.first_timestamp) parts.push("seen first " + clockTime(f.first_timestamp));
if (f.last_timestamp) parts.push("last " + clockTime(f.last_timestamp));
if (parts.length === 0) { host.style.display = "none"; return; }
host.style.display = "";
host.textContent = parts.join(" · ");
}
function clockTime(iso) {
return typeof iso === "string" && iso.length >= 19 ? iso.slice(11, 19) + " UTC" : (iso || "");
}
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) {
return (span.event_type || "") + "|" + (span.template || span.target || "");
}
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 treeHasTplMatch = false;
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;
treeHasTplMatch = !!targetTpl && spans.some(function (s) { return s.template === targetTpl; });
var host = root.closest ? root.closest(".ps-tree") : null;
setAttr(host || root, "data-fsev", finding.severity || "");
emitSpanTreeRows(root, spans, tree, targetTpl);
}
function buildSpanRow(span, depth, targetTpl) {
var row = el("div", "ps-span");
var isFinding = treeHasTplMatch && targetTpl && span.template === targetTpl;
if (isFinding) row.classList.add("hilite");
else if (treeHasTplMatch) 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");
highlightInto(text, describeSpan(span));
row.appendChild(text);
row.appendChild(el("span", "ps-span-dur", formatDurationUs(span.duration_us)));
return row;
}
function describeSpan(span) {
if (span.event_type === "sql") {
return span.template || span.target || span.operation || "(sql)";
}
if (span.event_type === "http_out") {
if (span.template) return span.template;
return (span.operation || "GET").toUpperCase() + " " + (span.target || "");
}
return 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) {
var card = metricCard(label, value, sub);
var v = card.querySelector(".ps-metric-value");
if (v) setAttr(v, "data-tone", tone || "brand");
setAttr(card, "data-grad", tone || "brand");
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) 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)
));
metrics.appendChild(greenCard("Operational CO2", formatGco2(co2.operational_gco2), "E × I"));
metrics.appendChild(greenCard("Embodied CO2", formatGco2(co2.embodied_gco2), "SCI v1.0 · M term"));
var avoidMid = co2.avoidable.mid;
metrics.appendChild(greenCard(
"Avoidable",
formatGco2(avoidMid),
percent(avoidMid, co2.operational_gco2) + " of operational",
avoidMid > 0 ? "accent" : "neutral"
));
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)
));
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)
));
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;
metrics.appendChild(greenCard(
"Database waste",
dbValue,
dbSub,
dbw.waste_kwh > 0 ? "accent" : "neutral"
));
}
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) {
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;
renderDiffFindingsList("diff-new-list", diffData.new_findings || [], true);
renderDiffFindingsList("diff-resolved-list", diffData.resolved_findings || [], "resolved");
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 renderCorrelationsPanel() {
if (!hasCorrelations) return;
var list = document.getElementById("correlations-list");
list.textContent = "";
correlations.forEach(function (c) {
var src = c.source || {};
var tgt = c.target || {};
var card = el("div", "ps-corr-card ps-correlation-row");
var tag = el("span", "ps-corr-tag", typeLabel(src.finding_type || "cross-trace").toUpperCase());
setAttr(tag, "data-sev", "warn");
card.appendChild(tag);
card.appendChild(
el("span", "ps-corr-path", (src.service || "?") + " → " + (tgt.service || "?"))
);
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")
);
if (c.sample_trace_id) {
card.classList.add("ps-correlation-clickable");
card.addEventListener("click", function () {
openExternalFindingDetail(syntheticFindingFromCorrelation(c));
});
} else {
card.style.cursor = "default";
}
list.appendChild(card);
});
syncCorrelationsEmpty();
}
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 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", "template",
"occurrences", "first_timestamp", "last_timestamp",
"code_function", "code_filepath", "code_lineno",
"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 || "",
p.template || "",
p.occurrences ?? "",
f.first_timestamp || "",
f.last_timestamp || "",
loc.function || "",
loc.filepath || "",
loc.lineno ?? "",
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;
}
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" });
}
}
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;
});
}
if (hasMysqlStat) {
registerTab("mysqlstat", "mysql_stat", function () {
return activeMysqlStatEntries().length;
});
}
if (hasDiff) {
registerTab("diff", "Diff", function () {
return (
(diffData.new_findings || []).length + (diffData.resolved_findings || []).length
);
});
}
if (hasCorrelations) {
registerTab("correlations", "Correlations", function () {
return correlations.length;
});
}
if (hasGreen) registerTab("green", "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();
if (hasGreen) renderGreenPanel();
}
function wireCheatsheetDialog() {
var closeBtn = document.getElementById("cheatsheet-close");
if (closeBtn) closeBtn.addEventListener("click", closeCheatsheet);
var dlg = document.getElementById("cheatsheet");
if (!dlg) return;
dlg.addEventListener("close", restorePriorFocus);
dlg.addEventListener("click", function (ev) {
if (ev.target === dlg) closeCheatsheet();
});
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) {
if (!btn.dataset.baseLabel) btn.dataset.baseLabel = btn.textContent;
var baseLabel = btn.dataset.baseLabel;
var href = location.href;
var tsort = tableSortHashValue();
if (location.hash && tsort) {
href = href.replace(/([&#])tsort=[^&]*/g, "$1").replace(/[&#]$/, "");
href += "&tsort=" + encodeURIComponent(tsort);
}
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 = href;
ta.setAttribute("readonly", "");
ta.style.position = "absolute";
ta.style.left = "-9999px";
document.body.appendChild(ta);
ta.select();
var ok = document.execCommand("copy");
ta.remove();
if (ok) {
flashCopied();
} else {
console.error("copyCurrentLink: execCommand('copy') returned false");
}
} catch (err) {
console.error("copyCurrentLink fallback failed", err);
}
}
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
navigator.clipboard.writeText(href).then(flashCopied, function (err) {
console.error("navigator.clipboard.writeText rejected", err);
fallbackCopy();
});
} else {
fallbackCopy();
}
}
function wireCopyLinkButtons() {
["findings", "pgstat", "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();
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>