<!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>
<style>
{{FONT_FACES}}
:root {
--font-ui: 'Geist', system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-mono: 'Geist Mono', 'JetBrains Mono', 'SF Mono', ui-monospace, Consolas, monospace;
--font-sans: var(--font-ui);
--r: 11px; --r-sm: 7px;
}
:root[data-theme="dark"] {
--bg: #0c100e; --surface: #141a17; --surface-2: #1a221d; --surface-3: #212b25;
--border: rgba(255,255,255,.35); --border-2: rgba(255,255,255,.5);
--text: #e8ede9; --text-2: #aabbb3; --text-3: #8a988f;
--brand: #2fd49a; --brand-2: #0ba671; --brand-text: #2fd49a; --accent-bg: rgba(47,212,154,.10);
--crit-fg: #ff8d8d; --crit-bg: rgba(255,99,99,.12); --crit-bd: rgba(255,99,99,.26);
--warn-fg: #f2b65f; --warn-bg: rgba(242,170,80,.12); --warn-bd: rgba(242,170,80,.24);
--info-fg: #74b8ff; --info-bg: rgba(96,166,255,.12); --info-bd: rgba(96,166,255,.24);
--ok-fg: #46d6a0; --ok-bg: rgba(47,212,154,.13); --ok-bd: rgba(47,212,154,.28);
--glow: radial-gradient(120% 140% at 0% 0%, rgba(47,212,154,.10), transparent 55%);
}
:root[data-theme="light"] {
--bg: #f3f6f4; --surface: #ffffff; --surface-2: #f6f9f7; --surface-3: #eef2f0;
--border: rgba(11,40,30,.55); --border-2: rgba(11,40,30,.66);
--text: #0f1f18; --text-2: #4d5d56; --text-3: #5f6d65;
--brand: #0ba671; --brand-2: #0b523a; --brand-text: #0a7850; --accent-bg: rgba(11,166,113,.09);
--crit-fg: #bf2a22; --crit-bg: #fbeceb; --crit-bd: #f0cdca;
--warn-fg: #8a5a14; --warn-bg: #f7efdc; --warn-bd: #e8d4ab;
--info-fg: #1f5e9e; --info-bg: #e9f0f8; --info-bd: #c8dbef;
--ok-fg: #0a7a52; --ok-bg: #e7f4ee; --ok-bd: #a6d4be;
--glow: radial-gradient(120% 140% at 0% 0%, rgba(11,166,113,.07), transparent 55%);
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body { background: var(--bg); color: var(--text); font-family: var(--font-ui); font-size: 13px; 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; }
a { color: var(--brand-text); }
[data-sev] { font-family: var(--font-mono); font-weight: 600; letter-spacing: .4px; }
[data-sev="crit"] { color: var(--crit-fg); background: var(--crit-bg); border: 1px solid var(--crit-bd); }
[data-sev="warn"] { color: var(--warn-fg); background: var(--warn-bg); border: 1px solid var(--warn-bd); }
[data-sev="info"] { color: var(--info-fg); background: var(--info-bg); border: 1px solid var(--info-bd); }
[data-sev="ok"] { color: var(--ok-fg); background: var(--ok-bg); border: 1px solid var(--ok-bd); }
[data-tone="crit"] { color: var(--crit-fg); }
[data-tone="ok"] { color: var(--ok-fg); }
[data-tone="warn"] { color: var(--warn-fg); }
[data-tone="neutral"] { color: var(--text); }
[data-tone="brand"] { color: var(--brand); }
[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: 1px solid var(--border); 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: 0; padding: 8px 10px; 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: 8px 9px; border-radius: var(--r-sm); font-size: 13px; font-weight: 500; color: var(--text-2); background: transparent; border: 0; cursor: pointer; transition: background .13s, color .13s, transform .13s; }
[data-nav]:hover { color: var(--text); background: var(--surface-2); transform: translateX(3px); }
[data-nav]:hover > .ps-nav-dot { background: var(--brand); opacity: 1; }
[data-nav] + [data-nav] { border-top: 1px solid var(--border); }
[data-nav][aria-selected="true"] { color: var(--text); background: var(--accent-bg); box-shadow: inset 2px 0 0 var(--brand); }
.ps-nav-dot { width: 6px; height: 6px; border-radius: 2px; background: currentColor; opacity: .85; flex: none; }
.ps-nav-label { flex: 1; }
.ps-nav-badge { font-family: var(--font-mono); font-size: 11px; 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: 14px 28px; border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--bg) 82%, 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: 17px; 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: 11px; font-family: var(--font-mono); font-weight: 600; white-space: nowrap; }
.ps-gate-pass { color: var(--ok-fg); background: var(--ok-bg); border: 1px solid var(--ok-bd); }
.ps-gate-fail { color: var(--crit-fg); background: var(--crit-bg); border: 1px solid var(--crit-bd); }
.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; border: 1px solid var(--border); border-radius: var(--r-sm); padding: 7px 11px; font-size: 12px; color: var(--text-2); background: transparent; font-family: inherit; }
[data-ghost]:hover { border-color: var(--border-2); color: var(--text); }
.ps-theme-toggle { display: inline-flex; align-items: center; gap: 7px; border: 1px solid var(--border); border-radius: var(--r-sm); padding: 7px 11px; font-size: 12px; color: var(--text-2); background: transparent; cursor: pointer; font-family: inherit; }
.ps-theme-toggle:hover { border-color: var(--border-2); color: var(--text); }
.ps-theme-toggle .ps-th-svg { flex: none; }
.ps-theme-toggle .ps-th-svg.accent { color: var(--brand-text); }
.ps-search { display: none; width: 100%; padding: 8px 11px; font-size: 12.5px; font-family: var(--font-ui); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); color: var(--text); outline: none; }
.ps-search:focus { border-color: var(--brand); }
.ps-search-box { display: flex; align-items: center; gap: 8px; border: 1px solid var(--border); background: var(--surface); border-radius: var(--r-sm); padding: 0 11px; height: 34px; flex: 0 0 390px; min-width: 0; }
.ps-search-box .ps-search-glyph { color: var(--text-3); font-size: 13px; }
.ps-search-box .ps-search { display: flex; width: auto; flex: 1; min-width: 0; border: 0; background: transparent; padding: 0; }
.ps-search-box .ps-search-kbd { font-family: var(--font-mono); font-size: 11px; color: var(--text-3); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; }
.ps-card { border: 1px solid var(--border); border-radius: var(--r); background: var(--surface); overflow: 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 var(--border); border-radius: var(--r); background: var(--surface); padding: 15px 16px; display: flex; flex-direction: column; gap: 9px; }
.ps-metric[data-grad="crit"] { background-image: linear-gradient(180deg, var(--crit-bg), transparent); }
.ps-metric[data-grad="warn"] { background-image: linear-gradient(180deg, var(--warn-bg), transparent); }
.ps-metric[data-grad="ok"] { background-image: linear-gradient(180deg, var(--ok-bg), transparent); }
.ps-metric[data-grad="brand"] { background-image: linear-gradient(180deg, var(--accent-bg), transparent); }
.ps-metric-label { font-family: var(--font-mono); font-size: 11px; letter-spacing: .9px; text-transform: uppercase; color: var(--text-3); }
.ps-metric-value { font-size: 27px; font-weight: 600; letter-spacing: -.6px; 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: 11px; color: var(--text-3); }
.ps-hero { display: grid; grid-template-columns: 300px 1fr; overflow: hidden; }
.ps-hero-left { padding: 24px 26px; border-right: 1px solid var(--border); background: linear-gradient(180deg, var(--accent-bg), transparent); }
.ps-hero-left[data-tone="crit"] { background: linear-gradient(180deg, var(--crit-bg), transparent); }
.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: -1px; 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: 11px; 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: 11px; 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 var(--border); border-radius: var(--r); background: var(--surface); padding: 15px 18px; }
.ps-corr-tag { padding: 3px 8px; border-radius: 5px; font-size: 11px; }
.ps-corr-path { font-family: var(--font-mono); font-size: 12.5px; color: var(--text); }
.ps-corr-meta { font-family: var(--font-mono); font-size: 11px; 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 var(--border); border-radius: var(--r); background: linear-gradient(180deg, var(--accent-bg), transparent); padding: 18px 22px; }
.ps-formula-code { font-family: var(--font-mono); font-size: 17px; color: var(--text); font-weight: 500; background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--r-sm); 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: 11px; 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: 15px 18px; border-bottom: 1px solid var(--border); }
.ps-overview-card-title { font-size: 13px; font-weight: 600; color: var(--text); }
.ps-overview-link { font-family: var(--font-mono); font-size: 11px; color: var(--text-2); background: transparent; border: 0; cursor: pointer; }
.ps-overview-link:hover { color: var(--text); }
.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: 11px; color: var(--text-3); }
.ps-mini-card { border: 1px solid var(--border); 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: 13px; 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: 11px; }
.ps-mini-link { font-family: var(--font-mono); font-size: 11px; color: var(--text-2); background: transparent; border: 0; cursor: pointer; align-self: flex-start; }
.ps-mini-link:hover { color: var(--text); }
.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: 11px; }
.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: 4px 11px; font-size: 11px; border-radius: 20px; border: 1px solid var(--border); background: transparent; cursor: pointer; color: var(--text-2); font-family: var(--font-mono); }
.ps-chip:hover { background: var(--surface-2); }
.ps-chip.active { background: var(--text); color: var(--bg); border-color: var(--text); }
.ps-chip[data-sev] { font-weight: 600; }
.ps-chip[data-sev="crit"] { color: var(--crit-fg); background: var(--crit-bg); border-color: var(--crit-bd); }
.ps-chip[data-sev="warn"] { color: var(--warn-fg); background: var(--warn-bg); border-color: var(--warn-bd); }
.ps-chip[data-sev="info"] { color: var(--info-fg); background: var(--info-bg); border-color: var(--info-bd); }
.ps-chip[data-sev].active { box-shadow: inset 0 0 0 1px currentColor; }
.ps-findings-count { font-family: var(--font-mono); font-size: 11px; color: var(--text-3); margin-left: 6px; }
.ps-list { display: flex; flex-direction: column; }
.ps-row { display: flex; flex-direction: column; gap: 7px; padding: 12px 14px; border-bottom: 1px solid var(--border); cursor: pointer; transition: background .12s; }
.ps-row:hover { background: var(--surface-2); }
.ps-row.selected { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--brand); }
.ps-row:last-child { border-bottom-color: transparent; }
.ps-sev { padding: 2px 6px; border-radius: 4px; font-size: 11px; }
.ps-badge-estimated, .ps-badge-measured { display: inline-block; padding: 2px 7px; border-radius: 4px; font-size: 11px; font-family: var(--font-mono); font-weight: 600; }
.ps-badge-estimated { color: var(--warn-fg); background: var(--warn-bg); border: 1px solid var(--warn-bd); }
.ps-badge-measured { color: var(--ok-fg); background: var(--ok-bg); border: 1px solid var(--ok-bd); }
.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: 11px; 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: 11px; font-family: var(--font-mono); }
.ps-fin-detail { font-size: 11px; color: var(--text-2); font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ps-fin-right { font-size: 11px; color: var(--text-2); font-family: var(--font-mono); text-align: right; white-space: nowrap; }
.ps-fin-right-sub { color: var(--text-3); }
.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 var(--border); 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: 1px solid var(--border); border-radius: var(--r-sm); 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-3); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 12px 14px; overflow: auto; white-space: pre; }
.ps-section { display: flex; flex-direction: column; gap: 8px; }
.ps-tree-header { font-size: 11px; color: var(--text-2); padding: 10px 12px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); font-family: var(--font-mono); word-break: break-all; }
.ps-tree { border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-2); padding: 12px 14px; display: flex; flex-direction: column; gap: 8px; }
.ps-span { display: flex; align-items: center; gap: 9px; padding: 1px 0; border-radius: 4px; }
.ps-span.dim { color: var(--text-3); }
.ps-span.hilite { background: var(--crit-bg); }
.ps-span-text { font-family: var(--font-mono); font-size: 11.5px; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ps-span-flag { padding: 1px 6px; border-radius: 4px; font-size: 11px; }
.ps-span-dur { font-family: var(--font-mono); font-size: 11px; color: var(--text-3); margin-left: auto; white-space: nowrap; }
.ps-span-count { flex: none; font-family: var(--font-mono); font-size: 10.5px; padding: 0 6px; border-radius: 5px; background: var(--surface-2); 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: 1px solid var(--ok-bd); border-radius: var(--r-sm); background: var(--ok-bg); padding: 12px 14px; display: flex; flex-direction: column; gap: 9px; }
.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: 1px solid var(--border); border-radius: var(--r-sm); 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; }
.ps-fix-code { font-family: var(--font-mono); font-size: 11.5px; color: var(--text); background: var(--surface); border: 1px solid var(--ok-bd); 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 var(--ok-bd); }
.ps-fix-link { align-self: flex-start; font-family: var(--font-mono); font-size: 11px; color: var(--brand-text); text-decoration: none; border-bottom: 1px solid var(--brand-text); }
.ps-source-link { color: var(--brand-text); text-decoration: underline; }
.ps-code-inline { font-family: var(--font-mono); font-size: .9em; background: var(--surface-3); border: 1px solid var(--border); border-radius: 4px; padding: 0 5px; color: var(--text); }
.ps-hl-kw { font-weight: 600; }
.ps-banner { background: var(--info-bg); border: 1px solid var(--info-bd); border-radius: var(--r-sm); padding: 8px 12px; font-size: 11px; color: var(--info-fg); }
.ps-empty { padding: 30px; text-align: center; color: var(--text-2); font-size: 13px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); }
.ps-drill { background: var(--info-bg); border: 1px solid var(--info-bd); border-radius: var(--r-sm); padding: 10px 12px; font-size: 12px; color: var(--info-fg); }
.ps-drill-label { font-size: 10px; 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: 11.5px; 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: .6px; text-transform: uppercase; font-family: var(--font-mono); }
.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; }
#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: 11px; color: var(--text-3); margin-right: 2px; }
.ps-pgstat-filter { margin-left: auto; height: 32px; flex: 0 1 260px; max-width: 260px; min-width: 180px; }
.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: 11px; color: var(--text-3); }
.ps-credit { margin-left: auto; }
.ps-credit a, .ps-footer a { color: var(--brand-text); text-decoration: none; border-bottom: 1px solid var(--brand-text); }
.ps-credit a:hover, .ps-footer a:hover { color: var(--text); }
.ps-kbd { font-family: var(--font-mono); font-size: 11px; border: 1px solid var(--border); border-radius: 4px; 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: 13px 18px; border-bottom: 1px solid var(--border); color: var(--text); }
.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: 0; }
.ps-section-title { font-size: 13px; 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 { padding: 6px 12px; font-size: 11.5px; border-radius: var(--r-sm); border: 1px solid var(--border); background: transparent; cursor: pointer; color: var(--text-2); font-family: inherit; }
.ps-export-btn:hover, .ps-copy-link-btn:hover { border-color: var(--border-2); color: var(--text); }
.ps-correlation-clickable { cursor: pointer; }
.ps-correlation-clickable:hover { background: var(--surface-2); }
.ps-show-more { padding: 12px 14px; font-size: 11px; border: 0; border-top: 1px solid var(--border); background: transparent; color: var(--brand-text); cursor: pointer; font-family: var(--font-mono); width: 100%; text-align: center; }
.ps-show-more:hover { background: var(--surface-2); }
@keyframes ps-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
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: var(--r); border: 1px solid var(--border-2); padding: 22px 24px; box-shadow: 0 24px 70px rgba(0,0,0,.45); }
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: 11px; 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: 11px; color: var(--text-3); margin: 12px 0 4px 0; font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .6px; }
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-2); border: 1px solid var(--border); border-radius: var(--r-sm); 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: var(--r-sm); 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(--ok-bg); color: var(--ok-fg); border: 1px solid var(--ok-bd); 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: 11px; 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: #2ea043; }
.ps-daemon-dot.disconnected { background: #cf222e; }
.ps-daemon-dot.unauthorized { background: #d4a72c; }
.ps-refresh-btn { display: none; padding: 7px 11px; font-size: 12px; border-radius: var(--r-sm); border: 1px solid var(--border); background: transparent; color: var(--text-2); cursor: pointer; font-family: inherit; }
body.ps-live .ps-refresh-btn { display: inline-block; }
body.ps-live .ps-refresh-btn[hidden] { display: none; }
.ps-refresh-btn:hover { border-color: var(--border-2); color: var(--text); }
.ps-refresh-btn:disabled { cursor: not-allowed; opacity: 0.6; }
.ps-fin-actions { display: none; margin-left: 8px; }
body.ps-live .ps-fin-actions { display: inline-flex; gap: 6px; }
.ps-fin-action-btn { padding: 5px 11px; font-size: 11px; border-radius: var(--r-sm); border: 1px solid var(--ok-bd); background: transparent; 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); color: var(--text-2); }
.ps-fin-action-btn.revoke:hover { background: var(--surface-2); }
.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; }
body.ps-live .ps-include-acked-wrap { display: inline-flex; }
.ps-acks-footer { margin-top: 10px; font-size: 11px; 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: 1px solid var(--border); border-radius: var(--r-sm); box-shadow: 0 4px 16px rgba(0,0,0,0.3); z-index: 9999; max-width: 380px; }
.ps-toast.error { border-color: var(--crit-bd); background: var(--crit-bg); color: var(--crit-fg); }
.ps-toast.success { border-color: var(--ok-bd); background: var(--ok-bg); color: var(--ok-fg); }
@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: nowrap;">
<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">Refresh</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="theme-toggle" type="button">Toggle theme</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>
<input type="search" class="ps-search" id="findings-search" placeholder="Filter findings by type, service, endpoint or template..." aria-label="Filter findings" />
<div class="ps-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">rank by</span>
<div class="ps-filters" id="pgstat-rankings"></div>
<div class="ps-search-box ps-pgstat-filter">
<span class="ps-search-glyph">⌕</span>
<input type="search" class="ps-search" id="pgstat-search" placeholder="Filter by SQL template…" aria-label="Filter pg_stat rows" />
</div>
<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 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-diff" role="tabpanel" aria-labelledby="tab-diff" class="ps-panel ps-panel-narrow" style="display: none;">
<input type="search" class="ps-search" id="diff-search" placeholder="Filter diff findings by type, service, endpoint or template..." aria-label="Filter diff findings" />
<div class="ps-panel-toolbar">
<button type="button" class="ps-export-btn" id="diff-export" data-export-tab="diff">Export CSV</button>
<button type="button" class="ps-copy-link-btn" id="diff-copy-link" data-copy-link-tab="diff">Copy link</button>
</div>
<div class="ps-diff-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>
<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>
</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;">
<input type="search" class="ps-search" id="correlations-search" placeholder="Filter correlations by service or type..." aria-label="Filter correlations" />
<div class="ps-panel-toolbar">
<button type="button" class="ps-export-btn" id="correlations-export" data-export-tab="correlations">Export CSV</button>
<button type="button" class="ps-copy-link-btn" id="correlations-copy-link" data-copy-link-tab="correlations">Copy link</button>
</div>
<div class="ps-list" id="correlations-list"></div>
</div>
<div id="panel-green" role="tabpanel" aria-labelledby="tab-green" 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> · eco-designed report · 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", "dark", "light"];
var prefersDarkMQ = null;
try {
prefersDarkMQ = globalThis.matchMedia
? globalThis.matchMedia("(prefers-color-scheme: dark)")
: null;
} catch {
prefersDarkMQ = null;
}
function currentThemeMode() {
var stored = sessionGet(STORAGE_KEYS.theme);
return THEME_MODES.includes(stored) ? stored : "auto";
}
function resolveThemeColor(mode) {
if (mode === "dark" || mode === "light") return mode;
return prefersDarkMQ && prefersDarkMQ.matches ? "dark" : "light";
}
function themeSvg(children, accent) {
var NS = "http://www.w3.org/2000/svg";
var svg = document.createElementNS(NS, "svg");
var base = { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none",
stroke: "currentColor", "stroke-width": "2.2", "stroke-linecap": "round",
"stroke-linejoin": "round", "class": accent ? "ps-th-svg accent" : "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.2" } },
{ tag: "path", attrs: { d: "M12 2.6v2.1M12 19.3v2.1M4.5 4.5l1.5 1.5M18 18l1.5 1.5M2.6 12h2.1M19.3 12h2.1M4.5 19.5l1.5-1.5M18 6l1.5-1.5" } }
], true);
}
if (mode === "dark") {
return themeSvg([{ tag: "path", attrs: { d: "M21 12.8A9 9 0 1 1 11.2 3 7 7 0 0 0 21 12.8z" } }], true);
}
return themeSvg([
{ tag: "circle", attrs: { cx: "12", cy: "12", r: "9" } },
{ tag: "path", attrs: { d: "M12 3 A9 9 0 0 0 12 21 Z", fill: "currentColor", stroke: "none" } }
], false);
}
function applyTheme(mode) {
document.documentElement.dataset.theme = resolveThemeColor(mode);
var btn = document.getElementById("theme-toggle");
if (btn) {
btn.textContent = "";
btn.appendChild(themeIcon(mode));
btn.appendChild(el("span", null, mode.charAt(0).toUpperCase() + mode.slice(1)));
}
}
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 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_]*!?)|(::|->|=>|[(){}\[\].,:;=<>!&|+\-*\/%?@]+)|(\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 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 === "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" },
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) {
if (typeof clearAllSearchFilters === "function") clearAllSearchFilters();
currentTab = key;
document.querySelectorAll(".ps-tab").forEach(function (btn) {
var active = btn.dataset.tab === key;
btn.classList.toggle("active", active);
setAttr(btn, "aria-selected", active ? "true" : "false");
setAttr(btn, "tabindex", active ? "0" : "-1");
});
tabs.forEach(function (t) {
var panel = document.getElementById("panel-" + t.key);
if (panel) panel.style.display = t.key === key ? "" : "none";
});
updateTopbarTitle(key);
if (key === "findings" && selectedFinding) {
var emptyState = document.getElementById("explain-empty");
if (emptyState && emptyState.style.display !== "none") 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 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;
root.appendChild(metricCard("Findings", fmtNum(total), "across " + fmtNum(traces) + " traces"));
root.appendChild(tonedValue(
metricCard("Critical", fmtNum(sev.critical), fmtNum(sev.warning) + " warn · " + fmtNum(sev.info) + " info"),
"crit"
));
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));
root.appendChild(dCard);
} else {
root.appendChild(metricCard("Δ baseline", "-", "no baseline"));
}
if (hasGreen) {
var co2 = greenSummary.co2;
root.appendChild(tonedValue(
metricCard("Total CO2", formatGco2(co2.total.mid),
"low " + formatGco2(co2.total.low) + " · high " + formatGco2(co2.total.high)),
"brand"
));
} 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");
findings.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) {
switchTab("findings");
var idx = visibleFindings.indexOf(f);
if (idx < 0) {
filterState.severity = null;
filterState.service = null;
syncFilterChips();
resetShownCount();
renderFindingsList();
idx = visibleFindings.indexOf(f);
}
if (idx >= 0) selectFinding(idx);
}
function openExternalFindingDetail(f) {
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();
reapplyFindingsSearch();
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 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 applyFilters() {
return findings.filter(function (f) {
if (filterState.severity && f.severity !== filterState.severity) return false;
if (filterState.service && f.service !== filterState.service) return false;
return true;
});
}
function renderFindingsList() {
var root = document.getElementById("findings-list");
var empty = document.getElementById("findings-empty");
root.textContent = "";
var filtered = applyFilters();
visibleFindings = filtered.slice(0, shownCount);
if (visibleFindings.length === 0) {
selectedFinding = null;
selectedIndex = -1;
empty.style.display = "";
resetDetailPane();
syncShowMore(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") openExplain(selectedFinding);
syncShowMore(filtered.length);
}
function resetDetailPane() {
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.classList.contains("ps-fin-action-btn") || t.closest && t.closest(".ps-fin-actions"))) {
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) {
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);
}
}
function renderTree(root, trace, finding) {
var spans = trace.spans || [];
if (spans.length === 0) {
root.appendChild(el("div", "ps-span dim", "(empty trace)"));
return;
}
var tree = buildSpanTree(spans);
var targetTpl = (finding.pattern && finding.pattern.template) || null;
emitSpanTreeRows(root, spans, tree, targetTpl);
}
function buildSpanRow(span, depth, targetTpl) {
var row = el("div", "ps-span");
var isFinding = targetTpl && span.template === targetTpl;
if (isFinding) row.classList.add("hilite");
else row.classList.add("dim");
row.style.paddingLeft = (depth * 20) + "px";
var pgStatMatch =
hasPgStat && span.event_type === "sql" && pgStatByTemplate[span.template]
? span.template
: null;
if (pgStatMatch) {
row.classList.add("ps-span-pgstat-link");
row.addEventListener("click", function () {
switchTab("pgstat");
showPgStatDrill(pgStatMatch);
});
}
var text = el("span", "ps-span-text");
if (isFinding) text.classList.add("ps-span-find");
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"
));
metrics.appendChild(greenCard(
"I/O waste ratio",
Math.round((greenSummary.io_waste_ratio || 0) * 100) + "%",
"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)
));
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 diffData = payload.diff || null;
var hasDiff = !!diffData;
var correlations =
report.correlations && report.correlations.length > 0 ? report.correlations : [];
var hasCorrelations = correlations.length > 0;
var pgStatRankings =
pgStat && Array.isArray(pgStat.rankings) ? pgStat.rankings : [];
var activeRankingIndex = 0;
var pgStatHiliteTemplate = null;
var PGSTAT_LABELS = {
"top by total_exec_time": "Total time",
"top by calls": "Calls",
"top by mean_exec_time": "Mean time",
"top by shared_blks_total": "I/O blocks"
};
function activePgStatEntries() {
if (pgStatRankings.length === 0) return [];
var idx = Math.min(activeRankingIndex, pgStatRankings.length - 1);
return pgStatRankings[idx].entries || [];
}
function renderPgStatPanel() {
if (!hasPgStat) {
document.getElementById("pgstat-empty").style.display = "";
return;
}
renderPgStatRankings();
renderPgStatTable();
}
function renderPgStatRankings() {
var root = document.getElementById("pgstat-rankings");
root.textContent = "";
if (pgStatRankings.length <= 1) {
root.style.display = "none";
return;
}
root.style.display = "";
setAttr(root, "role", "radiogroup");
setAttr(root, "aria-label", "pg_stat ranking");
pgStatRankings.forEach(function (r, idx) {
var label = PGSTAT_LABELS[r.label] || r.label || ("Ranking " + (idx + 1));
var btn = el("button", "ps-chip", label);
btn.type = "button";
setAttr(btn, "role", "radio");
setAttr(btn, "data-ranking-index", String(idx));
var isActive = idx === activeRankingIndex;
setAttr(btn, "aria-checked", isActive ? "true" : "false");
if (isActive) btn.classList.add("active");
btn.addEventListener("click", function () {
selectPgStatRanking(idx);
});
root.appendChild(btn);
});
}
function selectPgStatRanking(idx) {
sessionSet(STORAGE_KEYS.pgstatRanking, rankingSlugForIndex(idx));
if (idx === activeRankingIndex) return;
activeRankingIndex = idx;
var chips = document.querySelectorAll("#pgstat-rankings .ps-chip");
for (var i = 0; i < chips.length; i++) {
var isActive = i === idx;
chips[i].classList.toggle("active", isActive);
setAttr(chips[i], "aria-checked", isActive ? "true" : "false");
}
renderPgStatTable();
scheduleHashWrite();
if (pgStatHiliteTemplate) {
var stillPresent = activePgStatEntries().some(function (entry) {
return entry.normalized_template === pgStatHiliteTemplate;
});
if (stillPresent) {
highlightPgStatRow(pgStatHiliteTemplate);
} else {
clearPgStatDrill();
}
}
if (SEARCHABLE_TABS && SEARCHABLE_TABS.pgstat) {
applySearchFilter(SEARCHABLE_TABS.pgstat);
}
}
function renderPgStatTable() {
var body = document.getElementById("pgstat-body");
body.textContent = "";
activePgStatEntries().forEach(function (entry) {
var tr = document.createElement("tr");
setAttr(tr, "data-template", entry.normalized_template);
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);
});
}
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" });
}
function renderDiffPanel() {
if (!hasDiff) return;
renderDiffFindingsList(
"diff-new-list",
"diff-new-header",
"New findings",
diffData.new_findings || [],
true
);
renderDiffFindingsList(
"diff-resolved-list",
"diff-resolved-header",
"Resolved findings",
diffData.resolved_findings || [],
"resolved"
);
renderDiffSevTable(diffData.severity_changes || []);
renderDiffEndpTable(diffData.endpoint_metric_deltas || []);
}
function renderDiffFindingsList(listId, headerId, label, items, kind) {
var list = document.getElementById(listId);
var hdr = document.getElementById(headerId);
hdr.textContent = label + " (" + items.length + ")";
list.textContent = "";
items.forEach(function (f) {
list.appendChild(buildDiffFindingRow(f, kind));
});
}
function buildDiffFindingRow(f, kind) {
var row = el("div", "ps-row 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 + ")";
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 + ")";
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 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);
});
}
function syntheticFindingFromCorrelation(c) {
var tgt = c.target || {};
return {
trace_id: c.sample_trace_id,
service: tgt.service || "-",
source_endpoint: "-",
pattern: { template: tgt.template || "" }
};
}
function matchRowByText(row, q) {
return (row.textContent || "").toLowerCase().includes(q);
}
var SEARCHABLE_TABS = {
findings: {
inputId: "findings-search",
rowsSelector: "#findings-list .ps-row"
},
pgstat: {
inputId: "pgstat-search",
rowsSelector: "#pgstat-body tr"
},
diff: {
inputId: "diff-search",
rowsSelector: "#diff-new-list .ps-row, #diff-resolved-list .ps-row"
},
correlations: {
inputId: "correlations-search",
rowsSelector: "#correlations-list .ps-corr-card"
}
};
function initSearchInputs() {
Object.keys(SEARCHABLE_TABS).forEach(function (tabKey) {
var cfg = SEARCHABLE_TABS[tabKey];
var input = document.getElementById(cfg.inputId);
if (!input) return;
input.addEventListener("input", function () {
applySearchFilter(cfg);
var ts = document.getElementById("topbar-search");
if (ts) ts.value = input.value;
scheduleHashWrite();
});
});
wireTopbarSearch();
}
function wireTopbarSearch() {
var ts = document.getElementById("topbar-search");
if (!ts) return;
ts.addEventListener("input", function () {
var cfg = SEARCHABLE_TABS[currentTab];
if (!cfg) return;
var panelInput = document.getElementById(cfg.inputId);
if (panelInput) panelInput.value = ts.value;
applySearchFilter(cfg);
scheduleHashWrite();
});
}
function applySearchFilter(cfg) {
var input = document.getElementById(cfg.inputId);
var q = (input.value || "").toLowerCase();
document.querySelectorAll(cfg.rowsSelector).forEach(function (row) {
row.style.display = !q || matchRowByText(row, q) ? "" : "none";
});
}
function reapplyFindingsSearch() {
if (SEARCHABLE_TABS && SEARCHABLE_TABS.findings) {
applySearchFilter(SEARCHABLE_TABS.findings);
}
}
function openSearchForActiveTab() {
var cfg = SEARCHABLE_TABS[currentTab];
if (!cfg) return;
var ts = document.getElementById("topbar-search");
if (!ts) return;
var panelInput = document.getElementById(cfg.inputId);
ts.value = panelInput ? panelInput.value : "";
ts.focus();
ts.select();
}
function closeSearch(input) {
var cfg = SEARCHABLE_TABS[currentTab];
var ts = document.getElementById("topbar-search");
var panelInput = cfg ? document.getElementById(cfg.inputId) : null;
if (ts) ts.value = "";
if (panelInput) panelInput.value = "";
if (cfg) applySearchFilter(cfg);
if (input) input.blur();
}
function clearAllSearchFilters() {
Object.keys(SEARCHABLE_TABS).forEach(function (tabKey) {
var cfg = SEARCHABLE_TABS[tabKey];
var input = document.getElementById(cfg.inputId);
if (!input || !input.value) return;
input.value = "";
applySearchFilter(cfg);
});
var ts = document.getElementById("topbar-search");
if (ts) ts.value = "";
}
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);
});
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) {
var cfg = SEARCHABLE_TABS && SEARCHABLE_TABS[tabKey];
if (!cfg) return "";
var input = document.getElementById(cfg.inputId);
if (!input) return "";
return (input.value || "").toLowerCase();
}
function matchesSearch(haystack, term) {
if (!term) return true;
return String(haystack || "").toLowerCase().includes(term);
}
function exportFindingsCsv() {
var term = searchTermFor("findings");
var header = [
"severity", "type", "service", "endpoint", "template",
"occurrences", "first_timestamp", "last_timestamp",
"code_function", "code_filepath", "code_lineno",
"suggested_fix_framework", "suggested_fix_recommendation",
"confidence"
];
var rows = applyFilters().filter(function (f) {
var blob = [
f.severity, f.type || f.finding_type, f.service, f.source_endpoint,
f.pattern && f.pattern.template
].join(" ");
return matchesSearch(blob, term);
}).map(function (f) {
var p = f.pattern || {};
var loc = f.code_location || {};
var fix = f.suggested_fix || {};
return [
f.severity || "",
f.type || f.finding_type || "",
f.service || "",
f.source_endpoint || "",
p.template || "",
p.occurrences ?? "",
f.first_timestamp || "",
f.last_timestamp || "",
loc.function || "",
loc.filepath || "",
loc.lineno ?? "",
fix.framework || "",
fix.recommendation || "",
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) {
return matchesSearch(e.normalized_template, term);
}).map(function (e) {
return [
e.normalized_template || "",
e.calls ?? "",
e.total_exec_time_ms ?? "",
e.mean_exec_time_ms ?? "",
e.rows ?? "",
e.shared_blks_hit ?? "",
e.shared_blks_read ?? "",
e.seen_in_traces ? "true" : "false"
];
});
downloadCsv("pgstat", buildCsv(header, rows));
}
function diffFindingMatchBlob(f) {
return [
f.severity, f.type || f.finding_type, f.service, f.source_endpoint,
f.pattern && f.pattern.template
].join(" ");
}
function exportDiffCsv() {
if (!hasDiff) return;
var term = searchTermFor("diff");
var header = [
"section", "severity", "type", "service", "endpoint", "template",
"occurrences", "before_severity", "after_severity",
"before_io_ops", "after_io_ops", "delta"
];
var rows = [];
(diffData.new_findings || []).forEach(function (f) {
if (!matchesSearch(diffFindingMatchBlob(f), term)) return;
var p = f.pattern || {};
rows.push([
"new", f.severity || "", f.type || f.finding_type || "",
f.service || "", f.source_endpoint || "", p.template || "",
p.occurrences ?? "",
"", "", "", "", ""
]);
});
(diffData.resolved_findings || []).forEach(function (f) {
if (!matchesSearch(diffFindingMatchBlob(f), term)) return;
var p = f.pattern || {};
rows.push([
"resolved", f.severity || "", f.type || f.finding_type || "",
f.service || "", f.source_endpoint || "", p.template || "",
p.occurrences ?? "",
"", "", "", "", ""
]);
});
(diffData.severity_changes || []).forEach(function (c) {
var f = c.finding || {};
if (!matchesSearch(diffFindingMatchBlob(f), term)) return;
var p = f.pattern || {};
rows.push([
"severity_change", "", f.type || f.finding_type || "",
f.service || "", f.source_endpoint || "", p.template || "",
p.occurrences ?? "",
c.before_severity || "", c.after_severity || "",
"", "", ""
]);
});
(diffData.endpoint_metric_deltas || []).forEach(function (d) {
var blob = [d.service, d.endpoint].join(" ");
if (!matchesSearch(blob, term)) return;
rows.push([
"endpoint_delta", "", "",
d.service || "", d.endpoint || "", "",
"", "", "",
d.before_io_ops ?? "",
d.after_io_ops ?? "",
d.delta ?? ""
]);
});
downloadCsv("diff", buildCsv(header, rows));
}
function exportCorrelationsCsv() {
var term = searchTermFor("correlations");
var header = [
"service_a", "type_a", "template_a",
"service_b", "type_b", "template_b",
"confidence", "co_occurrence_count",
"source_total_occurrences", "median_lag_ms",
"first_seen", "last_seen"
];
var rows = correlations.filter(function (c) {
var src = c.source || {};
var tgt = c.target || {};
var blob = [
src.service, src.finding_type, tgt.service, tgt.finding_type
].join(" ");
return matchesSearch(blob, term);
}).map(function (c) {
var s = c.source || {};
var t = c.target || {};
return [
s.service || "", s.finding_type || "", s.template || "",
t.service || "", t.finding_type || "", t.template || "",
c.confidence ?? "",
c.co_occurrence_count ?? "",
c.source_total_occurrences ?? "",
c.median_lag_ms ?? "",
c.first_seen || "", c.last_seen || ""
];
});
downloadCsv("correlations", buildCsv(header, rows));
}
var RANKING_SLUGS = ["total_time", "calls", "mean_time", "io_blocks"];
function rankingSlugForIndex(idx) {
return RANKING_SLUGS[idx] || String(idx);
}
function rankingIndexForSlug(slug) {
if (RANKING_SLUGS.includes(slug)) return RANKING_SLUGS.indexOf(slug);
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 };
if (currentTab && SEARCHABLE_TABS[currentTab]) {
var term = searchTermFor(currentTab);
if (term) state.search = term;
}
if (currentTab === "findings") {
if (filterState.severity) state.severity = filterState.severity;
if (filterState.service) state.service = filterState.service;
}
if (currentTab === "pgstat") {
state.ranking = rankingSlugForIndex(activeRankingIndex);
}
return state;
}
function encodeHashFromState(state) {
if (!state || !state.tab) return "";
var parts = [state.tab];
["search", "ranking", "severity", "service"].forEach(function (k) {
var v = state[k];
if (v !== null && v !== undefined && v !== "") {
parts.push(k + "=" + encodeURIComponent(String(v)));
}
});
return parts.join("&");
}
var _lastWrittenHash = null;
function writeHash(state) {
var hashStr = encodeHashFromState(state);
var full = hashStr ? "#" + hashStr : "";
try {
history.replaceState(null, "", full || location.pathname + location.search);
_lastWrittenHash = hashStr;
} catch {
location.hash = hashStr;
_lastWrittenHash = hashStr;
}
}
var hashWriteTimer = null;
function scheduleHashWrite() {
if (hashWriteTimer) clearTimeout(hashWriteTimer);
hashWriteTimer = setTimeout(function () {
hashWriteTimer = null;
writeHash(currentStateForHash());
}, 200);
}
function applyHash(state) {
if (!state || !state.tab) return false;
if (!tabIsRegistered(state.tab)) return false;
if (state.tab === "findings") {
filterState.severity = null;
filterState.service = null;
if (state.severity === "critical" || state.severity === "warning" || state.severity === "info") {
filterState.severity = state.severity;
}
if (state.service) filterState.service = state.service;
syncFilterChips();
resetShownCount();
renderFindingsList();
}
if (state.tab === "pgstat") {
var idx = rankingIndexForSlug(state.ranking || "");
if (idx >= 0 && idx < pgStatRankings.length) {
selectPgStatRanking(idx);
}
}
switchTab(state.tab);
if (state.search && SEARCHABLE_TABS[state.tab]) {
var cfg = SEARCHABLE_TABS[state.tab];
var input = document.getElementById(cfg.inputId);
if (input) {
input.value = state.search;
applySearchFilter(cfg);
}
var ts = document.getElementById("topbar-search");
if (ts) ts.value = state.search;
}
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"],
["/", "Open search for the active tab"],
["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)"],
["?", "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 (isSearchInputFocused() || !SEARCHABLE_TABS[currentTab]) return;
openSearchForActiveTab();
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 (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 (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();
reapplyFindingsSearch();
});
}
function wireExportButtons() {
var exportHandlers = {
findings: exportFindingsCsv,
pgstat: exportPgStatCsv,
diff: exportDiffCsv,
correlations: exportCorrelationsCsv
};
Object.keys(exportHandlers).forEach(function (tabKey) {
var btn = document.getElementById(tabKey + "-export");
if (btn) btn.addEventListener("click", exportHandlers[tabKey]);
});
}
function copyCurrentLink(btn) {
if (!btn.dataset.baseLabel) btn.dataset.baseLabel = btn.textContent;
var baseLabel = btn.dataset.baseLabel;
var href = location.href;
function flashCopied() {
btn.textContent = "Copied";
if (btn._copyFlashTimer) clearTimeout(btn._copyFlashTimer);
btn._copyFlashTimer = setTimeout(function () {
btn._copyFlashTimer = null;
btn.textContent = baseLabel;
}, 1500);
}
function fallbackCopy() {
try {
var ta = document.createElement("textarea");
ta.value = href;
ta.setAttribute("readonly", "");
ta.style.position = "absolute";
ta.style.left = "-9999px";
document.body.appendChild(ta);
ta.select();
var ok = document.execCommand("copy");
ta.remove();
if (ok) {
flashCopied();
} else {
console.error("copyCurrentLink: execCommand('copy') returned false");
}
} catch (err) {
console.error("copyCurrentLink fallback failed", err);
}
}
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
navigator.clipboard.writeText(href).then(flashCopied, function (err) {
console.error("navigator.clipboard.writeText rejected", err);
fallbackCopy();
});
} else {
fallbackCopy();
}
}
function wireCopyLinkButtons() {
["findings", "pgstat", "diff", "correlations"].forEach(function (tabKey) {
var btn = document.getElementById(tabKey + "-copy-link");
if (!btn) return;
btn.addEventListener("click", function () {
copyCurrentLink(btn);
});
});
}
function applyInitialHashOrWrite() {
var initialHash = readHash();
if (applyHash(initialHash)) return;
writeHash(currentStateForHash());
}
function wireHashChangeListener() {
globalThis.addEventListener("hashchange", function () {
var raw = (location.hash || "").replace(/^#/, "");
if (raw === _lastWrittenHash) return;
applyHash(readHash());
});
}
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 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));
});
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);
};
});
applyAckedFilter();
}
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();
showToast("success", "Acknowledged.");
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();
syncFindingActionButtons();
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-badge");
if (badge) {
badge.textContent = String(count);
} else {
btn.appendChild(document.createTextNode(" "));
btn.appendChild(el("span", "ps-badge", String(count)));
}
}
function wireRefreshButton() {
var btn = document.getElementById("ps-refresh-btn");
if (!btn) return;
btn.addEventListener("click", function () {
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 applyAckedFilter() {
var input = document.getElementById("findings-include-acked");
if (!input) return;
var hideAcked = !input.checked;
var rows = document.querySelectorAll("#findings-list .ps-row[data-sig]");
rows.forEach(function (row) {
var sig = row.dataset.sig || "";
var isAcked = !!liveState.acksBySig[sig];
if (hideAcked && isAcked) row.style.display = "none";
else row.style.display = "";
});
}
function wireIncludeAckedToggle() {
var input = document.getElementById("findings-include-acked");
if (!input) return;
input.addEventListener("change", applyAckedFilter);
}
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();
wireHashChangeListener();
bootLiveMode();
})();
</script>
</body>
</html>