<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#f2f1ed">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'">
<title>Supercov report</title>
<style>
/* One system, shared with supercov.com: paper ground, white cards, ink
type set tight, mono rails, orange for what is open, blue for what the
reader can do. Dark mode is the same system on the site's ink ground. */
:root {
color-scheme: light;
--bg: #f2f1ed;
--bg-deep: #e9e8e3;
--card: #ffffff;
--card-2: #f6f5f2;
--ink: #10141c;
--ink-2: #1a2130;
--muted: #59627a;
--faint: #8d95a8;
--rule: rgba(16, 20, 28, .12);
--rule-soft: rgba(16, 20, 28, .07);
--accent: #0071e3;
--accent-hover: #0062c4;
--accent-soft: #e6f0fd;
--open: #e0680a;
--open-ink: #b44f00;
--open-soft: #fbead9;
--good: #1f8a4c;
--good-fill: #2fb35f;
--good-soft: #e3f5ea;
--bad: #c8321f;
--bad-fill: #e5442f;
--bad-soft: #fde9e6;
--limit: #8944ab;
--limit-fill: #a35cc7;
--limit-soft: #f2e7f8;
--code-bg: #ffffff;
--code-bg-2: #f6f5f2;
--code-ink: #10141c;
--code-muted: #8d95a8;
--code-rule: rgba(16, 20, 28, .08);
--code-selected: rgba(224, 104, 10, .1);
--code-assert: rgba(0, 113, 227, .08);
--record-bg: #ffffff;
--record-bg-2: #f6f5f2;
--record-ink: #10141c;
--record-muted: #59627a;
--record-rule: rgba(16, 20, 28, .08);
--record-accent: #b44f00;
--shadow: 0 1px 2px rgba(16, 20, 28, .05), 0 14px 34px rgba(16, 20, 28, .07);
--shadow-lg: 0 2px 4px rgba(16, 20, 28, .06), 0 30px 70px rgba(16, 20, 28, .14);
--radius: 14px;
--sans: "SF Pro Display", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Helvetica, Arial, sans-serif;
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
:root[data-theme="dark"] {
color-scheme: dark;
--bg: #0f1218;
--bg-deep: #0a0d12;
--card: #171b23;
--card-2: #1e232d;
--ink: #f2f4f7;
--ink-2: #e2e6ec;
--muted: #98a1b4;
--faint: #6f788c;
--rule: rgba(255, 255, 255, .11);
--rule-soft: rgba(255, 255, 255, .07);
--accent: #4aa3ff;
--accent-hover: #6cb5ff;
--accent-soft: rgba(74, 163, 255, .14);
--open: #ff8f2b;
--open-ink: #ffa452;
--open-soft: rgba(255, 143, 43, .14);
--good: #4cd07d;
--good-fill: #3bbf6d;
--good-soft: rgba(76, 208, 125, .14);
--bad: #ff6b5c;
--bad-fill: #ff5a49;
--bad-soft: rgba(255, 107, 92, .14);
--limit: #c58cf0;
--limit-fill: #b174e6;
--limit-soft: rgba(197, 140, 240, .14);
--code-bg: #0a0d12;
--code-bg-2: #10141c;
--code-ink: #e8ebf0;
--code-muted: #6f788c;
--code-rule: rgba(255, 255, 255, .08);
--record-bg: #10141c;
--record-bg-2: #171c26;
--record-ink: #f2f4f7;
--record-muted: #98a1b4;
--record-rule: rgba(255, 255, 255, .11);
--record-accent: #ff8f2b;
--shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 14px 34px rgba(0, 0, 0, .28);
--shadow-lg: 0 2px 4px rgba(0, 0, 0, .3), 0 30px 70px rgba(0, 0, 0, .4);
}
*, *::before, *::after { box-sizing: border-box; }
html { min-width: 320px; background: var(--bg); -webkit-text-size-adjust: 100%; }
body { margin: 0; color: var(--ink); background: var(--bg); font: 400 14px/1.5 var(--sans); letter-spacing: -.006em; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }
button, input, select { font: inherit; color: inherit; letter-spacing: inherit; }
button, select { cursor: pointer; }
button { border: 0; background: none; padding: 0; text-align: left; }
h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -.035em; line-height: 1.05; text-wrap: balance; }
p { margin: 0; text-wrap: pretty; }
::selection { background: #ffd9b0; color: #10141c; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.muted { color: var(--muted); }
.small { font-size: 12px; }
.mono { font-family: var(--mono); }
/* ---------- rails, pills, buttons ---------- */
.rail { display: flex; align-items: center; gap: 9px; margin: 0 0 14px; font: 600 11px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }
.rail::before { content: ""; width: 16px; height: 1px; background: currentColor; opacity: .6; }
.pill { display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 10px; border-radius: 999px; font-size: 12px; font-weight: 600; letter-spacing: -.01em; white-space: nowrap; color: var(--muted); background: var(--card-2); }
.pill.good { color: var(--good); background: var(--good-soft); }
.pill.fair, .pill.warn, .pill.open { color: var(--open-ink); background: var(--open-soft); }
.pill.weak, .pill.bad { color: var(--bad); background: var(--bad-soft); }
.pill.limited { color: var(--limit); background: var(--limit-soft); }
.pill.accent { color: var(--accent); background: var(--accent-soft); }
.pill i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; height: 36px; padding: 0 16px; border-radius: 10px; color: #fff; background: var(--ink); font-size: 13px; font-weight: 550; letter-spacing: -.01em; box-shadow: 0 1px 2px rgba(16, 20, 28, .16); transition: transform .18s cubic-bezier(.2, .7, .3, 1), background .18s; }
.button:hover { background: #000; transform: translateY(-1px); }
:root[data-theme="dark"] .button { color: #10141c; background: #fff; }
:root[data-theme="dark"] .button:hover { background: #fff; color: #000; }
.button.primary { color: #fff; background: var(--accent); }
.button.primary:hover { background: var(--accent-hover); color: #fff; }
.button.alt { color: var(--ink); background: var(--card); border: 1px solid var(--rule); box-shadow: var(--shadow); }
.button.alt:hover { background: var(--card); border-color: var(--rule); color: var(--ink); }
.link { display: inline-flex; align-items: center; gap: 6px; color: var(--accent); font-size: 12px; font-weight: 600; letter-spacing: -.01em; }
.link:hover { color: var(--accent-hover); }
.back-button { display: inline-flex; align-items: center; gap: 6px; margin: 0 0 22px; color: var(--accent); font-size: 12px; font-weight: 600; }
.back-button:hover { color: var(--accent-hover); }
.card-action, .missing-action, .prompt-copy-button, .prompt-close { display: inline-flex; align-items: center; height: 30px; padding: 0 12px; border: 1px solid var(--rule); border-radius: 999px; background: var(--card); color: var(--ink); font-size: 12px; font-weight: 600; letter-spacing: -.01em; box-shadow: 0 1px 2px rgba(16, 20, 28, .05); transition: border-color .15s, transform .15s; }
.card-action:hover, .missing-action:hover, .prompt-copy-button:hover, .prompt-close:hover { border-color: var(--ink); transform: translateY(-1px); }
.card-action { color: var(--accent); }
.card-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.file-chevron { color: var(--faint); font-size: 18px; font-weight: 400; line-height: 1; }
input[type="search"] { width: 100%; height: 40px; padding: 0 14px; border: 1px solid var(--rule); border-radius: 10px; background: var(--card); color: var(--ink); box-shadow: 0 1px 2px rgba(16, 20, 28, .04); }
input[type="search"]::placeholder { color: var(--faint); }
select { height: 34px; padding: 0 30px 0 12px; border: 1px solid var(--rule); border-radius: 999px; background: var(--card); color: var(--ink); font-size: 12px; font-weight: 600; appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.5l3 3 3-3' fill='none' stroke='%238d95a8' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 11px center; }
/* ---------- shell ---------- */
.report-layout { min-height: 100vh; display: grid; grid-template-columns: 264px minmax(0, 1fr); }
.run-sidebar { position: sticky; top: 0; height: 100vh; min-width: 0; display: flex; flex-direction: column; padding: 20px 14px 16px; border-right: 1px solid var(--rule-soft); background: var(--bg-deep); }
.sidebar-brand { display: flex; align-items: center; gap: 8px; padding: 0 8px; color: var(--ink); font-size: 15px; font-weight: 620; letter-spacing: -.035em; }
.sidebar-brand > span { flex: 1; }
.mark { width: 22px; height: 22px; display: block; flex: 0 0 auto; color: var(--ink); }
.sidebar-project { margin: 26px 8px 8px; overflow: hidden; color: var(--faint); font: 600 11px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-project-name { margin: 0 8px 18px; overflow: hidden; color: var(--ink); font-size: 14px; font-weight: 600; letter-spacing: -.02em; text-overflow: ellipsis; white-space: nowrap; }
.run-history { min-height: 0; flex: 1; overflow-y: auto; scrollbar-width: thin; margin: 0 -4px; padding: 0 4px; }
.run-list { display: grid; gap: 4px; }
.run-item { width: 100%; display: grid; grid-template-columns: 8px minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 11px 11px; border: 1px solid transparent; border-radius: 12px; color: var(--muted); background: transparent; transition: background .15s, border-color .15s, box-shadow .15s; }
.run-item:hover { color: var(--ink); background: var(--card); }
.run-item[aria-current="true"] { color: var(--ink); background: var(--card); border-color: var(--rule-soft); box-shadow: var(--shadow); }
.run-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--good-fill); }
.run-dot.warn { background: var(--open); }
.run-dot.bad { background: var(--bad-fill); }
.run-dot.quiet { background: var(--faint); }
.run-copy { min-width: 0; }
.run-date { display: block; overflow: hidden; color: inherit; font-size: 12.5px; font-weight: 600; letter-spacing: -.015em; text-overflow: ellipsis; white-space: nowrap; }
.run-context { display: block; margin-top: 2px; overflow: hidden; color: var(--faint); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
.run-score { color: inherit; font: 500 11px/1 var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.sidebar-foot { margin-top: 14px; padding: 12px 8px 0; color: var(--faint); font-size: 11px; line-height: 1.45; }
.theme-toggle { position: relative; width: 28px; height: 28px; flex: 0 0 auto; display: grid; place-items: center; border-radius: 8px; color: var(--faint); transition: color .15s, background .15s; }
.theme-toggle:hover { color: var(--ink); background: var(--card); }
.theme-icon { grid-area: 1 / 1; width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.theme-icon-moon { fill: currentColor; stroke: none; }
.theme-icon-sun { display: none; }
:root[data-theme="dark"] .theme-icon-moon { display: none; }
:root[data-theme="dark"] .theme-icon-sun { display: block; }
:root[data-theme="light"] .theme-icon-moon { display: block; }
:root[data-theme="light"] .theme-icon-sun { display: none; }
.report-main { width: 100%; max-width: 1180px; min-width: 0; margin: 0 auto; padding: 0 clamp(24px, 4.5vw, 64px) 96px; }
.tabs-bar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px 16px; position: sticky; top: 0; z-index: 20; margin: 0 calc(-1 * clamp(24px, 4.5vw, 64px)); padding: 16px clamp(24px, 4.5vw, 64px) 14px; background: linear-gradient(var(--bg) 78%, transparent); }
.tabs { display: flex; width: max-content; max-width: 100%; gap: 2px; padding: 3px; overflow-x: auto; border-radius: 999px; background: var(--bg-deep); border: 1px solid var(--rule-soft); scrollbar-width: none; }
.tab { position: relative; padding: 7px 14px; border-radius: 999px; color: var(--muted); font-size: 12.5px; font-weight: 600; letter-spacing: -.01em; white-space: nowrap; transition: color .15s, background .15s, box-shadow .15s; }
.tab:hover { color: var(--ink); }
.tab[aria-selected="true"] { color: var(--ink); background: var(--card); box-shadow: 0 1px 2px rgba(16, 20, 28, .08), 0 3px 10px rgba(16, 20, 28, .06); }
.report-content.standalone { margin-top: 26px; }
.panel { min-width: 0; }
.panel-title { font-size: 26px; font-weight: 600; letter-spacing: -.04em; line-height: 1.05; }
.collection-view { width: min(100%, 820px); }
.detail-view { width: min(100%, 980px); }
.empty { padding: 42px 20px; text-align: center; color: var(--muted); }
/* ---------- overview ---------- */
.hero { padding: 22px 0 0; display: grid; gap: 22px; }
.record { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px 16px; min-width: 0; color: var(--muted); font-size: 12.5px; }
.record-context { display: flex; flex: 1; flex-wrap: wrap; align-items: center; gap: 8px 0; min-width: 0; }
.record-context > * + *::before { content: "·"; display: inline-block; margin: 0 10px; color: var(--faint); font-family: var(--sans); }
.record-context code { color: var(--ink); font: 12px var(--mono); overflow-wrap: anywhere; }
.record-context .git-state { color: var(--muted); }
.record-command { display: inline-flex; align-items: center; gap: 5px; padding: 0; color: var(--muted); font-size: inherit; white-space: nowrap; }
.record-command:hover { color: var(--ink); }
.record-command:hover .record-command-label { text-decoration: underline; text-underline-offset: 3px; }
.record-command .jmark, .record-command svg { width: 14px; height: 14px; }
.instruments { display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; }
.instrument { display: flex; flex-direction: column; gap: 14px; min-width: 0; padding: 22px 22px 20px; border: 1px solid var(--rule-soft); border-radius: 16px; background: var(--card); box-shadow: var(--shadow); }
.instrument.missing { background: transparent; border: 1px dashed var(--rule); box-shadow: none; }
.instrument > .rail { margin: 0; }
.figure { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px 12px; }
.figure-number { font-size: clamp(38px, 4.2vw, 50px); font-weight: 600; line-height: .95; letter-spacing: -.05em; font-variant-numeric: tabular-nums; }
.figure-number.open { color: var(--open-ink); }
.figure-unit { color: var(--muted); font-size: 15px; font-weight: 500; letter-spacing: -.02em; }
.figure-note { color: var(--muted); font-size: 12.5px; line-height: 1.45; }
.figure-note strong { color: var(--ink); font-weight: 600; }
.instrument-list { display: grid; margin-top: 2px; }
.instrument-row { width: 100%; display: grid; grid-template-columns: 7px minmax(0, 1fr) auto 12px; gap: 10px; align-items: center; padding: 9px 4px; border-radius: 10px; box-shadow: none; color: inherit; }
.instrument-row:hover { background: var(--card-2); box-shadow: none; }
.instrument-row:last-child { box-shadow: none; }
.instrument-foot { margin-top: auto; padding-top: 12px; color: var(--faint); font-size: 11.5px; line-height: 1.45; }
.instrument-foot .link { font-size: 11.5px; }
.instrument .missing-action, .instrument .button { align-self: flex-start; }
.bar { position: relative; height: 4px; margin-top: 9px; border-radius: 999px; overflow: hidden; background: var(--rule-soft); }
.bar > span { display: block; height: 100%; border-radius: inherit; background: var(--good-fill); }
.bar.open > span { background: var(--open); }
/* ---------- marks: one silhouette per judgment ----------
A closed ring is coverage, which has a target. An arc that never closes is
assertion credit, whose ceiling is unknown. A dot on a scale is quality,
which is a ranking and so never fills. Shape carries the identity, colour
carries the value, and below 40px the coverage ring drops its arc and
carries state alone rather than drawing a sliver too thin to see. */
.jmark { display: inline-flex; flex: 0 0 auto; line-height: 0; }
.jmark svg { display: block; overflow: visible; }
.security-mark { color: var(--faint); }
.security-mark.open { color: var(--open-ink); }
.security-mark.clear { color: var(--good-fill); }
.jmark .track-quiet { stroke: var(--rule); }
.jmark .track-open { stroke: var(--open); }
.jmark .fill-cov { stroke: var(--good-fill); }
.jmark .state-clear { stroke: var(--good-fill); }
.jmark .state-open { stroke: var(--open); }
.jmark .halo { fill: var(--card); }
.jmark .tick { stroke: var(--faint); opacity: .5; }
.jmark .needle-good { fill: var(--good-fill); }
.jmark .needle-fair { fill: var(--open); }
.jmark .needle-weak { fill: var(--bad-fill); }
.jmark .ic-reached { stroke: var(--good-fill); }
.jmark .ic-left { stroke: var(--open); }
.jmark .ic-core { fill: var(--open); }
.instrument-head { display: flex; align-items: center; gap: 16px; min-width: 0; }
.instrument-head .figure-stack { display: grid; gap: 3px; min-width: 0; }
.instrument-head .figure { margin: 0; }
.figure-sub { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; color: var(--muted); font-size: 12.5px; }
.figure-sub .meta-count { font: 500 12px/1.4 var(--mono); color: var(--ink); font-variant-numeric: tabular-nums; }
.figure-sub .meta-note { color: var(--faint); font-size: 12px; }
.figure-sub .meta-note.open { color: var(--open-ink); font-weight: 600; }
/* The kinds are facets of one pool, so they read as a table, not as five
instruments. A kind with work behind it is a way in; a kind with none is a
statement, and is not a link. */
.kind-rows { display: grid; margin: 2px -22px 0; }
.kind-row { display: grid; grid-template-columns: minmax(0, 1fr) 54px 124px 14px; gap: 10px; align-items: center; width: 100%; padding: 9px 22px; color: inherit; text-align: left; transition: background .12s; }
.kind-row:last-child { }
.kind-row.go:hover { background: var(--card-2); }
.kind-row.go:hover .kind-go { opacity: 1; transform: translateX(2px); }
.kind-name { display: flex; align-items: center; gap: 9px; min-width: 0; overflow: hidden; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
.kind-row.go .kind-name { font-weight: 500; }
.kind-share { color: var(--muted); font: 500 12px/1 var(--mono); font-variant-numeric: tabular-nums; text-align: right; }
.kind-left { color: var(--faint); font: 400 11.5px/1 var(--mono); text-align: right; white-space: nowrap; }
.kind-left.open { color: var(--open-ink); font-weight: 500; }
.kind-go { color: var(--faint); font-size: 15px; line-height: 1; opacity: .5; transition: opacity .12s, transform .12s; }
/* A filter you cannot see is a filter you forget you turned on. */
.scope-note { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin-bottom: 14px; padding: 10px 14px; border-radius: 12px; background: var(--open-soft); color: var(--open-ink); font-size: 13px; }
.scope-note b { font-weight: 600; }
.scope-note .jmark { margin-right: 2px; }
.scope-note .link { margin-left: auto; }
.detail-stat-line { display: flex; align-items: center; gap: 9px; }
.detail-stat-line .jmark { flex: 0 0 auto; }
.file-head { display: grid; grid-template-columns: 8px minmax(0, 1fr) auto 12px; gap: 12px; align-items: center; padding: 4px 10px 8px; margin-bottom: 2px; }
.file-head::before { content: ""; }
.file-head-cell { display: inline-flex; align-items: center; gap: 4px; padding: 4px 0; color: var(--muted); font: 400 11.5px/1.4 var(--sans); white-space: nowrap; border-radius: 6px; }
.file-head-cell:hover { color: var(--ink); }
.file-head-cell.active { color: var(--ink); }
.file-head-arrow { width: 8px; font-size: 10px; line-height: 1; }
.file-head-marks { display: flex; align-items: center; gap: 16px; }
.file-head-marks .file-head-cell { min-width: 74px; }
.file-head-end { width: 12px; }
.file-row-marks { display: flex; align-items: center; gap: 16px; }
.file-row-mark { display: inline-flex; align-items: center; gap: 7px; min-width: 74px; color: var(--muted); font: 500 11px/1 var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.file-row-mark.open { color: var(--open-ink); font-weight: 600; }
.file-row-mark.quiet { color: var(--faint); }
.source-head-marks { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.ledger { display: grid; grid-template-columns: repeat(auto-fill, minmax(14px, 1fr)); gap: 4px; }
.file-mark { appearance: none; aspect-ratio: 1; min-width: 0; padding: 0; border: 0; border-radius: 3px; background: var(--good-fill); opacity: .82; transition: transform .16s, opacity .16s; }
.file-mark:hover { opacity: 1; transform: scale(1.35); }
.file-mark.open { background: var(--open); opacity: 1; }
.file-mark.limited { background: var(--limit-fill); opacity: 1; }
.file-mark.improved { box-shadow: 0 0 0 2px var(--card), 0 0 0 3.5px var(--good-fill); }
.ledger-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; font: 400 10.5px/1 var(--mono); color: var(--faint); letter-spacing: .02em; }
.ledger-legend span { display: inline-flex; align-items: center; gap: 6px; }
.ledger-legend i { width: 8px; height: 8px; border-radius: 2px; background: var(--good-fill); opacity: .82; }
.ledger-legend .o i { background: var(--open); opacity: 1; }
.ledger-legend .l i { background: var(--limit-fill); opacity: 1; }
.run-alert { display: inline-flex; align-items: center; gap: 8px; width: max-content; max-width: 100%; }
.next { padding: 22px 22px 8px; border: 1px solid var(--rule-soft); border-radius: 16px; background: var(--card); box-shadow: var(--shadow); }
.next .rail { margin-bottom: 10px; }
.next-title { font-size: 20px; letter-spacing: -.035em; }
.next-copy { margin-top: 6px; color: var(--muted); font-size: 13px; max-width: 60ch; }
.next-list { margin-top: 12px; display: grid; }
.next .link { margin: 10px 0 14px; }
/* ---------- lists ---------- */
.file-list-head { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 12px 18px; margin-bottom: 14px; }
.file-list-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; width: 100%; }
.file-list-tools input[type="search"] { flex: 1; min-width: 200px; }
.file-list { padding-right: 0; }
.file-row, .test-row { width: 100%; display: grid; grid-template-columns: 8px minmax(0, 1fr) auto 12px; gap: 12px; align-items: center; padding: 12px 10px; border-radius: 12px; color: inherit; box-shadow: none; transition: background .12s; }
.file-row:hover, .file-row[aria-current="true"], .test-row:hover, .test-row[aria-current="true"] { background: var(--card); box-shadow: var(--shadow); }
.file-row.static { cursor: default; }
.file-state-dot, .test-state-dot, .summary-dot, .status-dot { width: 7px; height: 7px; flex: 0 0 auto; border-radius: 50%; background: var(--good-fill); }
.file-state-dot.open, .test-state-dot.warn, .summary-dot.open { background: var(--open); }
.file-state-dot.limited, .summary-dot.limited { background: var(--limit-fill); }
.file-state-dot.good { background: var(--good-fill); }
.file-state-dot.fair { background: var(--open); }
.file-state-dot.weak, .test-state-dot.bad, .status-dot.bad { background: var(--bad-fill); }
.test-state-dot.neutral { background: var(--faint); }
.status-dot { margin-top: 5px; background: var(--muted); }
.status-dot.good { background: var(--good-fill); }
.file-row-identity, .test-row-identity, .relation-identity { min-width: 0; }
.file-row-name, .test-row-name, .relation-name { display: block; overflow: hidden; font-size: 13px; font-weight: 600; letter-spacing: -.015em; text-overflow: ellipsis; white-space: nowrap; }
.file-row-directory, .test-row-context, .relation-meta { display: block; margin-top: 2px; overflow: hidden; color: var(--faint); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
.file-row-directory.open { color: var(--open-ink); }
.file-row-trail { display: flex; align-items: center; gap: 14px; }
.file-row-metric, .test-row-metric, .relation-count { color: var(--muted); font-size: 11.5px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.file-row-metric.open { color: var(--open-ink); font-weight: 600; }
.file-row-metric.limited { color: var(--limit); font-weight: 600; }
.file-row-coverage { display: inline-flex; align-items: center; gap: 8px; min-width: 122px; color: var(--muted); font: 500 11px/1 var(--mono); font-variant-numeric: tabular-nums; }
.file-row-coverage .bar { width: 64px; margin: 0; }
.file-row-assert { display: inline-flex; align-items: center; gap: 5px; min-width: 84px; color: var(--muted); font: 500 11px/1 var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
.file-row-assert::before { content: "\25c6"; color: var(--accent); font-size: 9px; }
.file-row-assert.none { color: var(--faint); }
.file-row-assert.none::before { color: var(--faint); opacity: .5; }
.file-row-quality { min-width: 46px; font-size: 11.5px; font-weight: 600; text-transform: capitalize; }
.file-row-quality.good { color: var(--good); }
.file-row-quality.fair { color: var(--open-ink); }
.file-row-quality.weak { color: var(--bad); }
.test-filters { width: max-content; max-width: 100%; display: flex; gap: 2px; padding: 3px; overflow-x: auto; border-radius: 999px; background: var(--bg-deep); border: 1px solid var(--rule-soft); scrollbar-width: none; }
.test-filter { padding: 6px 12px; border-radius: 999px; color: var(--muted); font-size: 11.5px; font-weight: 600; white-space: nowrap; }
.test-filter:hover { color: var(--ink); }
.test-filter[aria-pressed="true"] { color: var(--ink); background: var(--card); box-shadow: 0 1px 2px rgba(16, 20, 28, .08); }
.test-group + .test-group { margin-top: 18px; }
.test-group-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; padding: 4px 10px 7px; }
.test-group-name { overflow: hidden; color: var(--muted); font-size: 11.5px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.test-group-count { color: var(--faint); font-size: 11px; white-space: nowrap; }
/* ---------- detail ---------- */
.detail { min-width: 0; }
.detail-top { padding: 0 0 6px; }
.detail-heading { max-width: 860px; font-size: clamp(28px, 3.6vw, 40px); letter-spacing: -.045em; overflow-wrap: anywhere; }
.detail-subtitle { margin-top: 6px; overflow-wrap: anywhere; color: var(--faint); font: 500 11.5px/1.4 var(--mono); }
.detail-summary { display: flex; flex-wrap: wrap; align-items: center; gap: 7px 10px; margin-top: 14px; color: var(--muted); font-size: 12.5px; }
.detail-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; margin-top: 18px; border: 1px solid var(--rule-soft); border-radius: 14px; background: var(--card); box-shadow: var(--shadow); overflow: hidden; }
.detail-stat { padding: 13px 16px 12px; background: var(--card); }
.detail-stat-label { color: var(--muted); font-size: 11.5px; font-weight: 600; }
.detail-stat-value { display: flex; align-items: baseline; gap: 8px; margin-top: 4px; font-size: 19px; font-weight: 600; letter-spacing: -.035em; line-height: 1.1; font-variant-numeric: tabular-nums; }
.detail-stat-value .pill { position: relative; top: -2px; }
.detail-stat-value.open { color: var(--open-ink); }
.detail-stat-value.quiet { color: var(--faint); font-weight: 500; }
.detail-stat-note { margin-top: 3px; color: var(--faint); font-size: 11px; }
.section { padding: 26px 0 0; }
.section h2, .section h3 { margin: 0 0 12px; font-size: 15px; letter-spacing: -.025em; }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; margin-bottom: 12px; }
.section-head h3 { margin: 0; }
.section-count { color: var(--faint); font-size: 11.5px; white-space: nowrap; }
.gap-lines { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 12px; }
.line-button { height: 28px; padding: 0 11px; border-radius: 999px; background: var(--card-2); color: var(--muted); font: 500 12px/1 var(--mono); }
.line-button:hover { color: var(--ink); }
.line-button[aria-current="true"] { color: var(--open-ink); background: var(--open-soft); }
.obligation { margin: 10px 0 0; padding: 12px 14px; border-left: 3px solid var(--open); border-radius: 0 12px 12px 0; background: var(--card); box-shadow: var(--shadow); }
.obligation-title { font-weight: 600; letter-spacing: -.015em; }
.obligation-copy { margin-top: 3px; color: var(--muted); font-size: 12.5px; overflow-wrap: anywhere; }
.relationship-jump { width: 100%; display: grid; grid-template-columns: minmax(0, 1fr) auto 12px; gap: 13px; align-items: center; margin-top: 26px; padding: 15px 16px; border: 1px solid var(--rule-soft); border-radius: 14px; color: inherit; background: var(--card); box-shadow: var(--shadow); }
.relationship-jump:hover { border-color: var(--rule); }
.relationship-jump-label { color: var(--muted); font-size: 11.5px; }
.relationship-jump-value { display: block; margin-top: 2px; font-size: 14px; font-weight: 600; letter-spacing: -.02em; }
.relation-section { padding: 26px 0 0; }
.relation-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 8px; }
.relation-head h3 { margin: 0; font-size: 15px; }
.relation-total { color: var(--faint); font-size: 11.5px; white-space: nowrap; }
.relation-list { display: grid; }
.relation-row { width: 100%; display: grid; grid-template-columns: minmax(0, 1fr) auto 12px; gap: 13px; align-items: center; padding: 11px 10px; border-radius: 11px; color: inherit; box-shadow: none; }
.relation-row:hover { background: var(--card-2); box-shadow: none; }
.relation-more { margin-top: 12px; color: var(--accent); font-size: 12px; font-weight: 600; }
.relation-empty { margin: 5px 0 0; color: var(--muted); font-size: 12.5px; }
.kv { display: grid; grid-template-columns: minmax(120px, 170px) 1fr; gap: 7px 14px; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; overflow-wrap: anywhere; }
/* ---------- source ---------- */
.source-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; margin-bottom: 10px; }
.source-head-copy { min-width: 0; display: grid; gap: 8px; }
.source-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; color: var(--muted); font-size: 11.5px; }
.source-key { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.source-key-symbol { width: 12px; text-align: center; font: 800 12px/1 var(--mono); color: var(--faint); }
.source-key-symbol.gap { color: var(--open); }
.source-key-symbol.covered { color: var(--good); }
.source-key-symbol.missed { color: var(--bad); }
.source-key-symbol.asserted { color: var(--accent); }
.source-key-symbol.declared { color: var(--faint); }
.code { overflow-x: auto; border: 1px solid var(--rule-soft); border-radius: 14px; color: var(--code-ink); background: var(--code-bg); box-shadow: var(--shadow); font: 12.5px/1.7 var(--mono); }
.code-row, .code-line { display: grid; grid-template-columns: 56px 14px 18px minmax(max-content, 1fr); min-height: 21px; }
.code-line { grid-template-columns: 56px 14px minmax(max-content, 1fr); }
.code-row.selected { background: var(--code-selected); box-shadow: inset 3px 0 var(--open); }
.code-row.open-gap .code-state { color: #ff8f2b; }
.code-row.covered .code-state { color: #4cd07d; }
.code-row.uncovered .code-state { color: #ff6b5c; }
.code-row.continuation .code-state, .code-row.continuation .code-assert { opacity: .42; }
.code-row.asserted-open { background: var(--code-assert); }
.code-no, .code-gutter { color: var(--code-muted); text-align: right; padding: 0 10px; border-right: 1px solid var(--code-rule); user-select: none; font-size: 11.5px; }
.code-state, .code-flag { text-align: center; font-weight: 800; color: var(--code-muted); }
.code-assert { text-align: center; font-weight: 800; color: var(--accent); padding: 0; font: inherit; line-height: inherit; }
.code-assert.declared { color: var(--code-muted); }
.code-assert[aria-expanded="true"] { color: #8fc5ff; }
.code-text { padding: 0 12px; white-space: pre; tab-size: 2; }
.code-expand { display: grid; grid-template-columns: 56px minmax(0, 1fr); min-height: 28px; align-items: center; background: var(--code-bg-2); color: var(--accent); font: 500 11.5px/1 var(--sans); }
.code-expand:first-child { }
.code-expand:last-child { }
.code-expand-button { height: 100%; padding: 0; text-align: center; color: inherit; border-right: 1px solid var(--code-rule); }
.code-expand-button:hover { background: rgba(74, 163, 255, .16); }
.code-expand-copy { padding: 0 12px; display: flex; gap: 14px; align-items: center; color: var(--code-muted); }
.code-expand-copy button { color: #4aa3ff; font: inherit; font-weight: 600; }
.code-annotation { grid-column: 1 / -1; margin: 8px 14px 14px 56px; border: 1px solid rgba(74, 163, 255, .35); border-radius: 12px; background: var(--card); color: var(--ink); font: 13px/1.5 var(--sans); white-space: normal; overflow: hidden; box-shadow: var(--shadow-lg); }
.annotation-head { display: flex; align-items: center; gap: 8px; padding: 9px 14px; background: var(--card-2); font-weight: 600; font-size: 12px; }
.annotation-head .code-assert { color: var(--accent); cursor: default; }
.annotation-card { padding: 12px 14px; display: grid; gap: 6px; }
.annotation-card:last-child { }
.annotation-assertion { font: 12px/1.55 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; }
.annotation-meta { color: var(--muted); font-size: 12px; }
.annotation-meta strong { color: var(--ink); font-weight: 600; font-family: var(--mono); font-size: 11.5px; }
.annotation-observes { margin: 0; padding-left: 18px; font-size: 12.5px; }
.annotation-explanation { font-size: 12.5px; color: var(--muted); }
.assertion-list { display: grid; gap: 4px; }
.assertion-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: center; width: 100%; padding: 6px 6px 6px 4px; border-radius: 12px; box-shadow: none; }
.assertion-row-jump { display: grid; grid-template-columns: minmax(0, 1fr) auto 12px; gap: 12px; align-items: center; min-width: 0; padding: 6px 8px; border-radius: 10px; color: inherit; }
.assertion-row-jump:hover { background: var(--card); box-shadow: var(--shadow); }
.assertion-row-text { font: 12px/1.55 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; }
.assertion-row-meta { color: var(--faint); font-size: 11px; margin-top: 3px; }
.decision { margin-top: 10px; border: 1px solid var(--rule-soft); border-radius: 12px; overflow: hidden; background: var(--card); }
.decision-head { padding: 10px 12px; background: var(--card-2); font: 500 12px/1.5 var(--mono); }
.decision-body { padding: 10px 12px; }
.condition { display: flex; align-items: flex-start; gap: 8px; padding: 5px 0; }
.vectors { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.vector { border-radius: 6px; color: var(--code-ink); background: var(--code-bg-2); border: 1px solid var(--rule-soft); padding: 3px 7px; font: 500 11px/1.5 var(--mono); }
/* ---------- quality and assertion cards ---------- */
.quality-panel, .assertion-panel { display: grid; gap: 12px; }
.quality-head { display: flex; align-items: center; gap: 10px; }
.quality-band { text-transform: capitalize; }
.quality-score { font-size: 24px; font-weight: 600; letter-spacing: -.04em; line-height: 1; font-variant-numeric: tabular-nums; }
.assertion-status { font-size: 12px; text-transform: capitalize; color: var(--muted); }
.assertion-status.good { color: var(--good); }
.quality-context { font-size: 12.5px; color: var(--muted); }
.quality-list { display: grid; }
.quality-row { display: grid; grid-template-columns: 7px minmax(0, 1fr) auto 12px; gap: 10px; align-items: center; width: 100%; padding: 9px 4px; border-radius: 10px; color: inherit; box-shadow: none; }
.quality-row:hover { background: var(--card-2); box-shadow: none; }
.quality-file { display: grid; gap: 12px; padding: 18px; border: 1px solid var(--rule-soft); border-radius: 14px; background: var(--card); box-shadow: var(--shadow); }
.quality-checks { display: grid; gap: 2px; }
.quality-check { display: flex; justify-content: space-between; gap: 12px; padding: 7px 10px; border-radius: 8px; background: var(--card-2); font-size: 12.5px; }
.quality-check-name { text-transform: capitalize; }
.quality-check-value { font: 500 12px/1.5 var(--mono); color: var(--open-ink); }
.assertion-files { font-size: 12.5px; color: var(--muted); }
.missing-card { display: grid; gap: 8px; justify-items: start; padding: 20px 22px; border: 1px dashed var(--rule); border-radius: 14px; }
.detail > .missing-card { margin-top: 26px; }
.missing-heading { font-weight: 600; letter-spacing: -.02em; }
.missing-copy { font-size: 12.5px; color: var(--muted); max-width: 52ch; }
.missing-action { margin-top: 4px; }
/* ---------- prompt ---------- */
.prompt-layer { position: fixed; inset: 0; z-index: 40; display: grid; place-items: center; padding: 24px; background: rgba(16, 20, 28, .5); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
.prompt-modal { width: min(560px, 100%); max-height: 88vh; overflow: auto; display: grid; gap: 14px; padding: 24px; border: 1px solid var(--rule-soft); border-radius: 16px; background: var(--card); box-shadow: var(--shadow-lg); }
.prompt-title { font-size: 21px; letter-spacing: -.035em; }
.prompt-copy { font-size: 13.5px; color: var(--muted); }
.prompt-command { margin: 0; padding: 14px 16px; border-radius: 12px; color: var(--record-ink); background: var(--record-bg-2); border: 1px solid var(--rule-soft); font: 12.5px/1.7 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; }
.prompt-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.prompt-copy-button { color: #fff; background: var(--accent); border-color: var(--accent); }
.prompt-copy-button:hover { border-color: var(--accent-hover); background: var(--accent-hover); }
.prompt-status { font-size: 12px; color: var(--muted); min-height: 16px; }
/* ---------- boot ---------- */
.fatal { max-width: 560px; margin: 16vh auto; padding: 34px; border: 1px solid var(--rule-soft); border-radius: 16px; background: var(--card); box-shadow: var(--shadow); text-align: center; }
/* Shown while the payload unpacks, before the script has chosen a theme:
it follows the reader's own setting until then. The mark has eightfold
symmetry, so an eighth of a turn loops without a seam. */
.opening { min-height: 100vh; display: grid; place-content: center; justify-items: center; gap: 8px; padding: 24px; text-align: center; color: var(--ink); }
.opening-mark { width: 44px; height: 44px; margin-bottom: 14px; color: var(--ink); animation: opening-turn 2.4s cubic-bezier(.65, 0, .35, 1) infinite; }
.opening-title { font-size: 17px; font-weight: 600; letter-spacing: -.01em; }
.opening-note { font-size: 13px; color: var(--muted); }
.opening-bar { width: 168px; height: 3px; margin-top: 16px; border-radius: 3px; background: var(--rule); overflow: hidden; }
.opening-bar span { display: block; width: 38%; height: 100%; border-radius: inherit; background: var(--accent); animation: opening-slide 1.3s ease-in-out infinite; }
@keyframes opening-turn { 0%, 25% { transform: rotate(0); } 60%, 100% { transform: rotate(45deg); } }
@keyframes opening-slide { from { transform: translateX(-110%); } to { transform: translateX(280%); } }
@media (prefers-reduced-motion: reduce) { .opening-mark, .opening-bar span { animation: none; } }
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) { color-scheme: dark; --bg: #0f1218; --ink: #f2f4f7; --muted: #98a1b4; --rule: rgba(255, 255, 255, .11); --accent: #4aa3ff; }
}
/* Overview: compact labels, left-aligned summaries, full-width evidence. */
.overview-label { font: 600 15px/1.3 var(--sans); letter-spacing: -.015em; color: var(--ink); margin: 0 0 16px; }
.overview-card { padding: 24px; gap: 0; }
.overview-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 320px)); gap: 32px; align-items: start; }
.overview-metric { min-width: 0; }
.overview-card .figure-number { font-size: 40px; }
.overview-card .figure-unit { font-size: 15px; font-weight: 400; color: var(--ink); }
.overview-card .figure-stack { gap: 5px; }
.overview-card .figure-sub { font: 12.5px/1.5 var(--sans); }
.overview-card .figure-sub .meta-count { font: inherit; color: var(--muted); }
.overview-card .instrument-head { gap: 16px; }
.overview-card .missing-heading { font-size: 20px; }
.overview-card .missing-copy { margin: 8px 0 12px; max-width: 440px; }
.run-test-summary { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; margin-top: 22px; color: var(--muted); font-size: 12.5px; }
.run-test-summary .test-result { display: inline-flex; align-items: center; gap: 7px; color: var(--good); }
.run-test-summary .test-result.bad { color: var(--bad); }
.run-test-summary .test-result.warn { color: var(--open-ink); }
.overview-notice { color: var(--open-ink); font-size: 12.5px; margin-top: 12px; }
.overview-files { margin-top: 22px; padding-top: 16px; }
.overview-list-title { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.overview-list-title h3 { font-size: 14px; letter-spacing: -.015em; }
.overview-list-title span { color: var(--muted); font-size: 11.5px; }
.overview-row { display: grid; grid-template-columns: 7px minmax(0, 1fr) 112px 112px 12px; gap: 12px; align-items: center; width: 100%; padding: 14px 4px; border-radius: 10px; box-shadow: none; }
.overview-row:hover { background: var(--card-2); }
.overview-row .file-row-name { font-size: 14px; }
.overview-row .file-row-directory { font-size: 12px; }
.overview-row .file-row-mark { min-width: 0; font-size: 12px; }
.overview-row-head { color: var(--muted); font-size: 11px; padding-top: 0; padding-bottom: 8px; border-radius: 0; }
.overview-row-head:hover { background: none; }
.overview-row-head .overview-file-label { grid-column: 2; }
.overview-quality-row { grid-template-columns: 7px minmax(0, 1.25fr) minmax(0, 1fr) 88px 12px; }
.overview-concerns { color: var(--muted); font-size: 12px; overflow-wrap: anywhere; }
.overview-footer { display: flex; justify-content: flex-end; margin-top: 14px; }
.overview-card .quality-band { text-transform: capitalize; }
.overview-card .quality-context { margin: 0; }
.security-empty { display: flex; align-items: center; gap: 14px; color: var(--muted); }
.security-empty strong { display: block; color: var(--ink); font-size: 18px; font-weight: 500; }
.security-empty p { font-size: 12.5px; margin-top: 3px; }
@media (max-width: 680px) {
.overview-card { padding: 20px 16px; }
.overview-metrics { grid-template-columns: minmax(0, 1fr); gap: 24px; }
.overview-row { grid-template-columns: 7px minmax(0, 1fr) 72px 72px 8px; gap: 7px; }
.overview-row .file-row-name { font-size: 12px; }
.overview-row .file-row-mark { gap: 4px; font-size: 10.5px; }
.overview-quality-row { grid-template-columns: 7px minmax(0, 1fr) 76px 8px; }
.overview-quality-row .overview-concerns { grid-column: 2; grid-row: 2; }
.overview-quality-row .file-row-mark { grid-column: 3; grid-row: 1; }
.overview-quality-row .file-chevron { grid-column: 4; grid-row: 1; }
.overview-quality-row.overview-row-head .overview-concerns { display: none; }
}
/* A list has one surface; its rows only have dividers and a quiet hover. */
.file-table { padding: 8px; background: var(--card); border: 1px solid var(--rule-soft); border-radius: 14px; box-shadow: var(--shadow); container-type: inline-size; }
.file-table .file-head { padding: 10px 12px; margin: 0; }
.file-table .file-row { padding: 11px 12px; border-radius: 9px; box-shadow: none; }
.file-table .file-row:last-child { }
.file-table .file-row:hover, .file-table .file-row[aria-current="true"] { background: var(--card-2); box-shadow: none; }
.overview-row { border-radius: 0; box-shadow: none; }
.overview-row:last-child { }
.overview-sort { display: flex; gap: 6px; align-items: center; padding: 4px 0; color: var(--muted); font-size: 11.5px; }
.overview-sort.active, .overview-sort:hover { color: var(--ink); }
.overview-sort:disabled { cursor: default; color: var(--faint); }
.detail-card { margin-top: 24px; padding: 22px; border: 1px solid var(--rule-soft); border-radius: 14px; background: var(--card); box-shadow: var(--shadow); }
.detail-card-head { display: flex; gap: 12px; align-items: baseline; justify-content: space-between; margin-bottom: 14px; }
.detail-card-head h3 { font-size: 15px; letter-spacing: -.015em; }
.detail-card-head .small { color: var(--muted); }
.evidence-list { display: grid; }
.evidence-row { display: grid; grid-template-columns: minmax(0, 1fr) auto 12px; gap: 14px; align-items: center; width: 100%; padding: 13px 12px; }
.evidence-row:last-child { }
.evidence-row:hover { background: var(--card-2); }
.evidence-name { display: block; font-size: 13px; font-weight: 550; overflow-wrap: anywhere; }
.evidence-meta { display: block; margin-top: 4px; color: var(--muted); font-size: 11.5px; overflow-wrap: anywhere; }
.evidence-code { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 4px; color: var(--muted); font: 11.5px/1.5 var(--mono); }
.evidence-more { margin-top: 12px; }
.file-quality-summary { display: flex; align-items: center; gap: 16px; margin-bottom: 20px; }
.file-quality-summary .quality-band { color: var(--muted); font-size: 13px; margin-top: 5px; }
.quality-concern { padding: 13px 0; }
.quality-concern strong { font-size: 13px; font-weight: 600; }
.quality-concern p { margin-top: 4px; color: var(--muted); font-size: 12.5px; }
.assessment-values { margin-top: 12px; }
.assessment-values > div { display: flex; justify-content: space-between; gap: 12px; padding: 6px 0; color: var(--muted); font-size: 12px; }
.code-row { grid-template-columns: 56px 24px 24px minmax(max-content, 1fr); }
.code-state, button.code-assert { border-radius: 3px; min-height: 22px; }
button.code-state:hover, button.code-assert:hover { background: var(--accent-soft); }
.code-assert.absent { color: var(--code-muted); }
.code-row.evidence-open { background: var(--code-assert); }
.code-annotation { margin: 0; padding: 18px 22px 20px 56px; border: 0; border-radius: 0; box-shadow: none; background: var(--card-2); max-width: 100%; }
.line-evidence-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; font-size: 13px; }
.line-evidence-section + .line-evidence-section { margin-top: 16px; padding-top: 16px; }
.line-evidence-section h4 { font-size: 13px; margin: 0 0 8px; letter-spacing: -.01em; }
.line-evidence-section > p { color: var(--muted); font-size: 12px; margin: 5px 0; }
.line-evidence-section .annotation-card { padding: 12px 0; }
.line-gap { padding: 8px 0; font-size: 12px; }
.line-gap strong { display: block; font-weight: 550; }
.line-gap p { color: var(--muted); margin-top: 4px; }
.line-test-row { display: block; width: 100%; padding: 6px 0; color: var(--accent); font-size: 12px; overflow-wrap: anywhere; }
@media (max-width: 680px) {
.detail-card { padding: 18px 16px; }
.code-annotation { padding: 16px; }
.evidence-row { gap: 8px; }
}
/* ---------- responsive ---------- */
@media (max-width: 1180px) {
.instruments { grid-template-columns: minmax(0, 1fr); }
.instruments > .instrument:first-child { grid-column: 1 / -1; }
}
@media (max-width: 980px) {
.report-layout { display: block; }
.run-sidebar { position: relative; height: auto; padding: 12px 16px; border-right: 0; }
.sidebar-brand { padding: 0; }
.sidebar-project, .sidebar-project-name, .sidebar-foot { display: none; }
.run-history { margin: 12px 0 0; overflow-x: auto; scrollbar-width: none; }
.run-list { display: flex; gap: 8px; }
.run-item { min-width: 190px; }
.tabs-bar { top: 0; }
.instruments { grid-template-columns: 1fr; }
}
@media (max-width: 680px) {
.report-main { padding: 0 16px 56px; }
.tabs-bar { margin: 0 -16px; padding: 12px 16px 10px; }
.record-head .live { margin-left: 0; }
.file-row-coverage { min-width: 0; }
.file-row-coverage .bar { display: none; }
.file-row-quality { min-width: 0; }
.kv { grid-template-columns: 1fr; gap: 2px; }
.code-annotation { margin-left: 0; }
.assertion-row { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; } }
/* Compact paths and shared table rows. */
.file-path { font-weight: 400; }
.path-directory { color: var(--muted); font-weight: 400; }
.path-basename { font-weight: 600; color: var(--ink); }
.file-row-name.file-path, .relation-name.file-path { text-overflow: clip; }
.detail-heading.file-path { font-size: clamp(22px, 2.6vw, 34px); }
.file-table .file-row { padding-top: 11px; padding-bottom: 11px; min-height: 48px; }
.file-table .file-row:hover, .file-table .file-row[aria-current="true"], .overview-row:not(.overview-row-head):hover, .evidence-row:hover, .line-test-row:hover { border-radius: 9px; background: var(--card-2); }
.overview-row { padding: 11px 12px; min-height: 48px; }
.overview-row-head { min-height: 32px; padding-top: 0; padding-bottom: 7px; }
.overview-files { container-type: inline-size; }
.overview-row { min-width: 0; }
.overview-quality-row { min-width: 0; grid-template-columns: 7px minmax(360px, 1fr) 300px 88px 12px; }
.overview-row .file-row-name { font-size: 13px; }
.overview-quality-row .overview-concerns, .overview-quality-row .file-row-mark, .overview-quality-row .file-chevron { grid-column: auto; grid-row: auto; }
.overview-quality-row.overview-row-head .overview-concerns { display: block; }
.concern-pills { display: flex; flex-wrap: wrap; gap: 5px; }
.concern-pill { display: inline-flex; align-items: center; gap: 5px; padding: 4px 7px; border: 1px solid var(--rule-soft); border-radius: 6px; background: var(--card-2); color: var(--muted); font-size: 10.5px; line-height: 1.3; white-space: nowrap; }
.concern-pill .jmark, .concern-pill svg { width: 13px; height: 13px; }
.subtle-button, .link, .back-button, .card-action, .missing-action, .relation-more, .code-expand-copy button { display: inline-flex; align-items: center; justify-content: center; min-height: 30px; padding: 5px 12px; border: 1px solid var(--rule); border-radius: 999px; background: var(--card); color: var(--ink); font-size: 12px; font-weight: 500; box-shadow: 0 1px 2px rgba(0,0,0,.03); }
.subtle-button:hover, .link:hover, .back-button:hover, .card-action:hover, .missing-action:hover, .relation-more:hover, .code-expand-copy button:hover { color: var(--ink); background: var(--card-2); border-color: var(--faint); transform: none; }
.coverage-breakdown.subtle-button { margin-top: 10px; align-self: flex-start; }
.code-row { grid-template-columns: 48px 34px minmax(max-content, 1fr); }
.code-evidence { display: flex; align-items: center; justify-content: center; gap: 3px; width: 32px; min-height: 23px; margin: 0 1px; border-radius: 5px; font: inherit; }
.code-evidence:hover, .code-evidence[aria-expanded="true"] { background: var(--accent-soft); }
.code-evidence .code-state, .code-evidence .code-assert { min-height: 0; font-size: 11px; width: 12px; }
.code-annotation { margin: 10px 14px 16px 48px; padding: 0; border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent); border-radius: 13px; background: var(--card); box-shadow: var(--shadow); }
.line-evidence-head { margin: 0; padding: 12px 16px; background: var(--card-2); }
.line-evidence-section { padding: 16px; }
.line-evidence-section + .line-evidence-section { margin: 0; padding: 16px; }
.line-test-row { padding: 7px 8px; }
/* Same dark glass treatment as the Supercov site, without remote assets. */
body.report-modal-open { overflow: hidden; }
.report-modal { --ink: #f7f7fa; --muted: #bdc2ce; --faint: #abb2c1; --rule-soft: rgba(255,255,255,.12); --card: #242830; --card-2: rgba(255,255,255,.065); --good: #67db92; --open-ink: #ffb16b; --accent: #69b4ff; color-scheme: dark; width: min(580px, calc(100vw - 32px)); max-height: calc(100dvh - 40px); margin: auto; padding: 28px; overflow-y: auto; overscroll-behavior: contain; border: 1px solid rgba(255,255,255,.24); border-radius: 28px; color: var(--ink); background: linear-gradient(145deg,rgba(255,255,255,.09),transparent 32%),radial-gradient(circle at 8% 12%,rgba(255,176,92,.13),transparent 31%),radial-gradient(circle at 92% 18%,rgba(105,151,255,.17),transparent 33%),radial-gradient(circle at 78% 92%,rgba(74,204,188,.12),transparent 31%),rgba(12,14,19,.88); box-shadow: 0 36px 100px rgba(0,0,0,.42),inset 0 1px 0 rgba(255,255,255,.2); backdrop-filter: blur(28px) saturate(180%); -webkit-backdrop-filter: blur(28px) saturate(180%); }
.report-modal::backdrop { background: rgba(11,13,18,.12); }
.report-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 18px; }
.report-modal h2 { font-size: 22px; letter-spacing: -.025em; }
.report-modal-close { flex: 0 0 34px; height: 34px; border-radius: 50%; background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.18); color: #fff; font-size: 25px; text-align: center; line-height: 1; }
.report-modal-close:hover { background: rgba(255,255,255,.18); }
.modal-description { color: var(--muted); font-size: 13px; line-height: 1.5; }
.modal-command { padding: 18px; margin: 20px 0; border: 1px solid var(--rule-soft); border-radius: 16px; background: rgba(255,255,255,.055); font: 12.5px/1.7 var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; user-select: text; }
.modal-primary { width: 100%; min-height: 42px; border-radius: 999px; background: #0a84ff; color: #fff; text-align: center; font-size: 13px; font-weight: 600; }
.modal-primary:hover { background: #2491ff; }
.modal-status { color: var(--muted); font-size: 12px; }
.modal-status:not(:empty) { margin-top: 12px; }
.report-modal .figure-sub { margin-top: 20px; display: flex; justify-content: space-between; gap: 16px; }
.report-modal .kind-rows { margin: 18px 0 0; border: 1px solid var(--rule-soft); border-radius: 16px; overflow: hidden; }
.report-modal .kind-row { padding: 14px; grid-template-columns: minmax(0,1fr) 45px 104px 10px; gap: 10px; }
.report-modal .kind-row:last-child { }
.report-modal .kind-row:hover { background: var(--card-2); border-radius: 9px; }
@media (max-width: 680px) {
.code-annotation { margin: 8px 8px 12px; padding: 0; }
.report-modal { padding: 20px; border-radius: 23px; }
.report-modal .kind-row { grid-template-columns: minmax(0,1fr) 36px 90px 8px; gap: 5px; padding: 12px 9px; font-size: 11px; }
}
/* Space and alignment separate rows; no horizontal divider rules. */
.file-state-dot { display: none; }
.overview-row { grid-template-columns: minmax(0, 1fr) 112px 112px 12px; }
.overview-quality-row { grid-template-columns: minmax(340px, 1fr) 340px 88px 12px; }
.overview-row-head .overview-file-label { grid-column: 1; }
.file-row:has(> .file-state-dot), .file-head { grid-template-columns: minmax(0, 1fr) auto 12px; }
.file-head::before { display: none; }
.overview-files { padding-top: 8px; margin-top: 26px; }
.overview-row-head { padding-bottom: 12px; }
.metric-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 30px; margin-bottom: 20px; }
.metric-heading .overview-label { margin: 0; }
.metric-heading .coverage-breakdown { margin: 0; }
.overview-card .instrument-head, .file-quality-summary { align-items: flex-start; }
.overview-card .instrument-head > .jmark { margin-top: 3px; }
.overview-metrics { grid-template-columns: repeat(2, minmax(0, 350px)); gap: 40px; }
.quality-rating { text-transform: capitalize; font: 600 12px/1.25 var(--sans); padding: 5px 9px; letter-spacing: 0; }
.figure .quality-rating { align-self: center; margin-left: 6px; }
.quality-score-line { display: flex; gap: 12px; align-items: center; }
.quality-concern { padding: 12px 0; }
.quality-concern-heading { display: flex; align-items: center; justify-content: space-between; gap: 20px; }
.quality-concern-value { padding: 4px 8px; border-radius: 6px; font: 500 12px/1.3 var(--mono); font-variant-numeric: tabular-nums; color: hsl(var(--concern-hue, 30) 72% 34%); background: hsl(var(--concern-hue, 30) 80% 95%); }
.concern-pill.scored { border-color: transparent; color: hsl(var(--concern-hue) 72% 34%); background: hsl(var(--concern-hue) 80% 95%); }
.concern-value { font: 500 10px/1.3 var(--mono); margin-left: 3px; font-variant-numeric: tabular-nums; }
:root[data-theme="dark"] .concern-pill.scored, :root[data-theme="dark"] .quality-concern-value { color: hsl(var(--concern-hue, 30) 90% 76%); background: hsl(var(--concern-hue, 30) 32% 20%); }
.jmark .quality-scale-weak { stroke: var(--bad-fill); }
.jmark .quality-scale-fair { stroke: #e9a23b; }
.jmark .quality-scale-good { stroke: var(--good-fill); }
.jmark .quality-scale-empty { stroke: var(--rule); }
.jmark .quality-pointer-halo { stroke: var(--card); }
.jmark .quality-pointer { stroke: var(--ink); }
.report-modal-close { display: grid; place-items: center; padding: 0; line-height: 0; }
.report-modal-close .jmark, .report-modal-close svg { display: block; width: 16px; height: 16px; }
.modal-command { margin-bottom: 0; }
@media (max-width: 680px) { .overview-metrics { grid-template-columns: minmax(0, 1fr); gap: 26px; } }
/* Lists share an inset surface, with padding inside each rounded hover. */
.test-table { padding: 8px; background: var(--card); border: 1px solid var(--rule-soft); border-radius: 14px; box-shadow: var(--shadow); }
.test-table .file-row, .test-table .test-row { padding: 13px 12px; border-radius: 9px; box-shadow: none; }
.test-table .file-row:hover, .test-table .test-row:hover { background: var(--card-2); box-shadow: none; }
.test-table .test-group-head { padding: 12px; }
.test-table .test-row-name { white-space: normal; overflow-wrap: anywhere; }
.test-detail-header .detail-heading { font-size: clamp(22px, 2.4vw, 30px); line-height: 1.25; }
.test-detail-header .detail-subtitle { overflow-wrap: anywhere; }
.test-detail-header .detail-summary { margin-bottom: 0; }
.test-covered-files .relation-row { padding: 12px; }
.evidence-list { display: grid; gap: 4px; }
.evidence-row { border-radius: 9px; text-align: left; }
.evidence-meta { margin-top: 6px; overflow-wrap: anywhere; }
.evidence-more.subtle-button { margin: 12px 8px 0 0; }
.evidence-modal { width: min(760px, calc(100vw - 32px)); }
.report-modal .modal-search { width: 100%; padding: 12px 14px; margin-bottom: 12px; border: 1px solid var(--rule-soft); border-radius: 12px; background: var(--card-2); color: var(--ink); font: 13px var(--sans); }
.report-modal .modal-search::placeholder { color: var(--muted); }
.modal-evidence > .evidence-list { margin-top: 12px; }
.modal-evidence .line-test-row { width: 100%; padding: 12px; border-radius: 9px; }
.report-modal .kind-rows { padding: 6px; }
/* Compact, comparable metrics for each saved moment. */
.run-item { grid-template-columns: minmax(0, 1fr); gap: 10px; padding: 12px; }
.run-metrics { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 5px; }
.run-metric { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px; min-width: 0; color: var(--muted); font: 500 10px/1.2 var(--mono); white-space: nowrap; font-variant-numeric: tabular-nums; }
.run-metric .jmark { flex: 0 0 auto; }
.run-metric.security-flagged { color: var(--open-ink); }
.report-modal h2:focus { outline: none; }
.line-evidence-head { background: transparent; padding-bottom: 0; }
.line-section-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px 16px; margin-bottom: 8px; }
.line-section-head h4 { margin: 0; }
.line-status { padding: 4px 8px; border-radius: 6px; background: var(--card-2); color: var(--muted); font-size: 11px; }
.line-tests { display: grid; gap: 4px; margin-top: 12px; }
.line-test-row { display: grid; grid-template-columns: minmax(0, 1fr) 12px; gap: 12px; align-items: center; padding: 10px 12px; border-radius: 9px; color: var(--ink); text-align: left; }
.line-test-identity { min-width: 0; }
.line-test-name { display: block; font-size: 12.5px; font-weight: 500; }
.line-test-file { display: block; margin-top: 4px; color: var(--muted); font-size: 11px; overflow-wrap: anywhere; }
.line-test-file .path-basename { color: var(--muted); font-weight: 400; }
.line-evidence-section .annotation-card { padding: 16px; margin-top: 12px; border-radius: 12px; background: var(--card-2); gap: 12px; }
.annotation-title { font: 600 13px/1.5 var(--sans); margin: 0; }
.annotation-explanation { margin: 0; font-size: 12px; }
.annotation-assertion { margin: 0; padding: 12px 14px; border-radius: 9px; background: var(--card); font: 11.5px/1.6 var(--mono); }
.annotation-footer { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px 20px; }
.annotation-footer .annotation-meta { flex: 1 1 240px; min-width: 0; }
.annotation-location { display: block; font-size: 11.5px; overflow-wrap: anywhere; }
.annotation-location strong { font: inherit; font-weight: 600; }
.annotation-test-name { display: block; margin-top: 4px; font-size: 11px; }
.annotation-footer .card-actions { margin: 0; }
@media (max-width: 980px) { .run-item { min-width: 232px; } }
.back-button { max-width: 100%; overflow-wrap: anywhere; text-align: left; }
.code-expand-copy { padding-top: 5px; padding-bottom: 5px; }
@media (max-width: 1100px) { .detail-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 420px) { .detail-stat { padding: 12px; } .detail-stat-value { font-size: 17px; gap: 5px; } }
.detail-heading.file-path { font-size: clamp(18px, 1.8vw, 24px); letter-spacing: -.025em; line-height: 1.4; }
.detail-stat-line { margin-top: 6px; min-height: 28px; align-items: center; }
.detail-stat-value { margin-top: 0; align-items: center; flex-wrap: wrap; }
.detail-stat-value .pill { top: 0; }
.overview-card .instrument-head, .file-quality-summary { align-items: center; }
.overview-card .instrument-head > .jmark { margin-top: 0; }
.metric-actions { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
.improve-button { gap: 6px; padding-inline: 10px; white-space: nowrap; }
.detail-stat .improve-button { margin-top: 12px; }
.agent-marks { display: inline-flex; align-items: center; isolation: isolate; }
.agent-marks .jmark { width: 18px; height: 18px; padding: 1px; border-radius: 50%; background: var(--card); }
.agent-marks .jmark + .jmark { margin-left: -5px; }
.agent-marks svg { display: block; width: 16px; height: 16px; }
.agent-marks .agent-codex svg { fill: currentColor; }
.improve-button:hover .agent-marks .jmark { background: var(--card-2); }
.improve-prompt { max-height: 45vh; overflow-y: auto; }
.improve-content .modal-primary { margin-top: 8px; }
.improve-scope { display: block; margin-top: 14px; font-size: 12px; overflow-wrap: anywhere; }
.quiet { color: var(--faint); }
.report-modal.prompt-modal{width:min(100%,430px);max-height:calc(100dvh - 36px);padding:20px;position:relative;overflow-y:auto;overscroll-behavior:contain;border:1px solid rgba(255,255,255,.24);border-radius:34px;background:linear-gradient(145deg,rgba(255,255,255,.09),transparent 32%),radial-gradient(circle at 8% 12%,rgba(255,176,92,.13),transparent 31%),radial-gradient(circle at 92% 18%,rgba(105,151,255,.17),transparent 33%),radial-gradient(circle at 78% 92%,rgba(74,204,188,.12),transparent 31%),rgba(12,14,19,.82);box-shadow:0 36px 100px rgba(0,0,0,.42),inset 0 1px 0 rgba(255,255,255,.2),inset 0 -1px 0 rgba(255,255,255,.06);-webkit-backdrop-filter:blur(28px) saturate(180%) brightness(.78);backdrop-filter:blur(28px) saturate(180%) brightness(.78);color:#f7f7fa;opacity:1;transition:opacity .2s ease,transform .26s cubic-bezier(.2,.78,.25,1);scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.2) transparent}
.prompt-modal-close{appearance:none;width:36px;height:36px;padding:0;position:absolute;right:14px;top:14px;z-index:2;display:grid;place-items:center;border:1px solid rgba(255,255,255,.14);border-radius:50%;background:rgba(255,255,255,.09);box-shadow:inset 0 1px 0 rgba(255,255,255,.1);color:#fff;cursor:pointer;transition:background .18s ease,transform .18s ease}
.prompt-modal-close:hover{background:rgba(255,255,255,.16)}.prompt-modal-close:active{transform:scale(.94)}.prompt-modal-close svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round}
.prompt-modal-step{margin-top:24px}.prompt-modal-step-first{margin-top:4px}.prompt-modal-step-head{min-height:32px;padding-right:45px;display:grid;grid-template-columns:32px minmax(0,1fr);align-items:center;gap:12px}.prompt-modal-step-head>span{width:30px;height:30px;display:grid;place-items:center;border:1px solid rgba(255,255,255,.16);border-radius:50%;background:rgba(255,255,255,.08);color:rgba(255,255,255,.83);font-size:.76rem;font-weight:520}.prompt-modal-step-head h2{margin:0;color:#fff;font-size:1rem;font-weight:610;line-height:1.2;letter-spacing:-.025em}
.prompt-modal-choices{margin:12px 0 0 44px;padding:0;overflow:hidden;border:1px solid rgba(255,255,255,.12);border-radius:21px;background:rgba(255,255,255,.055);box-shadow:inset 0 1px 0 rgba(255,255,255,.045)}
.prompt-modal-choice{appearance:none;width:100%;min-height:76px;padding:14px 16px;display:grid;grid-template-columns:18px minmax(0,1fr);align-items:center;gap:12px;border:0;background:transparent;color:inherit;text-align:left;cursor:pointer;transition:background .18s ease}.prompt-modal-choice+.prompt-modal-choice{border-top:1px solid rgba(255,255,255,.1)}.prompt-modal-choice:hover{background:rgba(255,255,255,.045)}.prompt-modal-choice.is-selected{background:rgba(255,255,255,.095)}
.prompt-modal-choice>input{appearance:none;width:16px;height:16px;margin:0;border:1.5px solid rgba(255,255,255,.42);border-radius:50%;background:transparent}.prompt-modal-choice>input:checked{background:radial-gradient(circle,#fff 0 4px,transparent 4.5px)}.prompt-modal-choice>input:focus-visible{outline:2px solid #2997ff;outline-offset:3px}.prompt-modal-choice>span{display:grid;gap:4px}.prompt-modal-choice strong{color:#fff;font-size:.91rem;font-weight:590;line-height:1.2}.prompt-modal-choice small{color:rgba(235,235,241,.68);font-size:.73rem;line-height:1.35}
.prompt-modal-copy-card{margin:12px 0 0 44px;padding:15px;border:1px solid rgba(255,255,255,.12);border-radius:21px;background:rgba(255,255,255,.055);box-shadow:inset 0 1px 0 rgba(255,255,255,.045)}.prompt-modal-preview{margin:0 0 13px;color:rgba(247,247,250,.9);font-size:.79rem;line-height:1.46;letter-spacing:-.012em}.prompt-modal-preview code{border:0;background:linear-gradient(100deg,#ff8a39 3%,#e84474 30%,#8366ee 56%,#168bd8 78%,#09a982);color:transparent;font:inherit;font-weight:650;-webkit-background-clip:text;background-clip:text}
.prompt-modal-copy{appearance:none;width:100%;min-height:42px;padding:0 16px;display:flex;align-items:center;justify-content:center;gap:8px;border:0;border-radius:999px;background:#0a84ff;box-shadow:0 10px 24px rgba(0,104,225,.24),inset 0 1px 0 rgba(255,255,255,.28);color:#fff;font:inherit;font-size:.78rem;font-weight:620;cursor:pointer;transition:background .18s ease,transform .18s ease}.prompt-modal-copy:hover{background:#188cff}.prompt-modal-copy:active{transform:scale(.985)}.prompt-modal-copy.is-copied{background:#22a868}.prompt-modal-copy svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.prompt-modal-instruction{margin:10px 0 0 44px;color:rgba(235,235,241,.68);font-size:.73rem;line-height:1.45}.prompt-modal-agents{margin:14px 0 0 44px;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));align-items:start;gap:12px}.prompt-modal-agents>span{min-width:0;display:grid;justify-items:center;gap:7px;color:rgba(245,245,248,.7)}.prompt-modal-agents img{width:28px;height:28px;object-fit:contain}.prompt-modal-agents>span:nth-child(2) img,.prompt-modal-agents>span:nth-child(3) img{filter:brightness(0) invert(1)}.prompt-modal-agents small{max-width:100%;font-size:.59rem;line-height:1.2;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.prompt-modal :focus-visible{outline:2px solid rgba(96,174,255,.92);outline-offset:3px}
@media (max-width:520px){.prompt-modal-layer{padding:10px}.report-modal.prompt-modal{max-height:calc(100dvh - 20px);padding:17px 16px 18px;border-radius:28px}.prompt-modal-close{right:12px;top:12px;width:34px;height:34px}.prompt-modal-step{margin-top:21px}.prompt-modal-step-first{margin-top:3px}.prompt-modal-step-head{grid-template-columns:30px minmax(0,1fr);gap:10px}.prompt-modal-step-head>span{width:28px;height:28px}.prompt-modal-choices,.prompt-modal-copy-card,.prompt-modal-instruction,.prompt-modal-agents{margin-left:40px}.prompt-modal-choice{min-height:70px;padding:12px 13px}.prompt-modal-choice small{font-size:.69rem}.prompt-modal-agents{gap:7px}.prompt-modal-agents img{width:25px;height:25px}}
.report-modal.prompt-modal { font-size: 16px; }
.prompt-modal > .report-modal-head { margin: 0; }
.prompt-modal .report-modal-head h2, .prompt-modal .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
.prompt-modal .prompt-modal-step-head h2 { font-size: 16px; }
.prompt-modal .prompt-modal-close { display: grid; flex: none; }
.prompt-modal .prompt-modal-close .jmark { display: contents; }
.prompt-modal .improve-prompt { max-height: none; overflow: visible; white-space: pre-wrap; overflow-wrap: anywhere; user-select: text; }
.prompt-modal .prompt-modal-copy-card .prompt-modal-copy { margin-top: 0; }
.prompt-copy-error { margin-top: 10px; color: #ffb16b; font-size: 12px; }
.report-modal .kind-row { grid-template-columns: minmax(0, 1fr) auto 10px; gap: 16px; padding: 12px 16px; }
.report-modal .kind-share { display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
.report-modal .kind-share .jmark, .report-modal .kind-share svg { width: 32px; height: 32px; }
.report-modal .kind-share > span:last-child { min-width: 48px; text-align: right; }
.breakdown-file { display: block; margin: 0 0 14px; font-size: 13px; overflow-wrap: anywhere; }
.detail-stat-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.detail-stat-actions .improve-button { margin-top: 0; }
/* Reflow lists by their available space, including beside the desktop sidebar. */
.test-table, .evidence-list, .test-covered-files { container-type: inline-size; }
.mobile-metric-label { display: none; }
@container (max-width: 940px) {
.overview-quality-row { grid-template-columns: minmax(0, 1fr) auto 12px; }
.overview-quality-row .file-row-identity { grid-column: 1; grid-row: 1; }
.overview-quality-row .file-row-mark { grid-column: 2; grid-row: 1; }
.overview-quality-row .file-chevron { grid-column: 3; grid-row: 1; }
.overview-quality-row .overview-concerns { grid-column: 1 / -1; grid-row: 2; }
.overview-quality-row.overview-row-head .overview-concerns { display: none; }
.overview-quality-row .file-row-name { white-space: normal; overflow-wrap: anywhere; }
}
@container (max-width: 760px) {
.overview-row:not(.overview-row-head), .file-table .file-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 12px; gap: 12px 16px; padding: 16px 12px; }
.overview-row .file-row-identity, .file-table .file-row-identity { grid-column: 1 / 3; grid-row: 1; }
.overview-row .file-chevron, .file-table .file-chevron { grid-column: 3; grid-row: 1; }
.overview-row .file-row-mark { grid-row: 2; grid-column: auto; }
.file-row-mark { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: auto 26px; justify-content: start; gap: 6px; min-width: 0; font-size: 11px; }
.mobile-metric-label { display: block; grid-column: 1 / -1; color: var(--muted); font: 400 10.5px/1.4 var(--sans); }
.file-row-marks { grid-column: 1 / -1; display: grid; align-items: start; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.overview-quality-row .file-row-mark { grid-column: 1 / 3; justify-self: start; }
.overview-quality-row .overview-concerns { grid-row: 3; }
.overview-row-head, .file-table .file-head { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 14px; padding: 4px 12px 8px; min-height: 0; }
.overview-row-head::before, .file-table .file-head::before { display: block; content: 'Sort by'; color: var(--faint); font-size: 11px; }
.overview-row-head .overview-file-label { grid-column: auto; }
.file-head-marks { display: contents; }
.file-head-end, .file-head > span:empty, .file-head-marks > span.file-head-cell { display: none; }
.file-head-marks .file-head-cell { min-width: 0; }
.file-head-cell, .overview-sort { padding-block: 8px; }
.file-row-name, .test-row-name, .relation-name, .file-row-directory, .test-row-context, .relation-meta, .evidence-name { white-space: normal; overflow-wrap: anywhere; text-overflow: clip; }
.test-table .file-row, .test-table .test-row { grid-template-columns: 8px minmax(0, 1fr) 12px; gap: 8px 10px; }
.test-table .file-row-identity, .test-table .test-row-identity { grid-column: 2; grid-row: 1; }
.test-table .test-state-dot { grid-column: 1; grid-row: 1; }
.test-table .file-chevron { grid-column: 3; grid-row: 1; }
.test-table .file-row-metric, .test-table .test-row-metric { grid-column: 2; grid-row: 2; }
.relation-row, .evidence-row { grid-template-columns: minmax(0, 1fr) 12px; gap: 6px 10px; }
.relation-row > :first-child, .evidence-row > :first-child { min-width: 0; grid-column: 1; }
.relation-row .file-chevron, .evidence-row .file-chevron { grid-column: 2; grid-row: 1; }
.relation-count { grid-column: 1; grid-row: 2; }
}
@container (max-width: 420px) {
.file-row-marks { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 680px) {
.test-filters { width: auto; flex-wrap: wrap; overflow: visible; }
.detail-card-head { flex-wrap: wrap; gap: 8px; }
}
.security-summary { display: flex; align-items: center; gap: 16px; margin-bottom: 20px; }
.security-summary strong { display: block; font-size: 24px; letter-spacing: -.025em; line-height: 1.2; }
.security-summary .quality-context { margin: 6px 0 0; }
.security-summary-icon { display: grid; place-items: center; flex: 0 0 56px; height: 56px; border-radius: 16px; background: var(--card-2); color: var(--muted); }
.security-summary.flagged .security-summary-icon { color: var(--open-ink); }
.security-summary.flagged .security-summary-icon { background: var(--open-soft); }
.security-summary.clear .security-summary-icon { color: var(--good); background: var(--good-soft); }
.security-file-link { color: inherit; font: inherit; text-align: left; overflow-wrap: anywhere; }
.security-file-link:hover { text-decoration: underline; text-underline-offset: 3px; }
.security-finding { margin-top: 16px; padding: 16px; border-radius: 12px; background: var(--card-2); }
.security-finding pre { white-space: pre-wrap; overflow-wrap: anywhere; margin-top: 10px; }
.security-file-heading { margin: 24px 0 12px; font-size: 14px; overflow-wrap: anywhere; }
</style>
</head>
<body>
<div id="app"><div class="opening" role="status" aria-live="polite"><svg class="opening-mark" viewBox="0 0 256 256" aria-hidden="true"><path fill="currentColor" d="M44 100h168a28 28 0 0 1 0 56H44a28 28 0 0 1 0-56Z"/><path fill="currentColor" d="M128 16a28 28 0 0 1 28 28v168a28 28 0 0 1-56 0V44a28 28 0 0 1 28-28Z"/><path fill="currentColor" d="M48.804 88.402 167.598 207.196A28 28 0 0 0 207.196 167.598L88.402 48.804A28 28 0 0 0 48.804 88.402Z"/><path fill="currentColor" d="M88.402 207.196 207.196 88.402A28 28 0 0 0 167.598 48.804L48.804 167.598A28 28 0 0 0 88.402 207.196Z"/></svg><span class="opening-title">Opening the report</span><span class="opening-note">Unpacking it in your browser. Nothing is uploaded.</span><span class="opening-bar" aria-hidden="true"><span></span></span></div></div>
<script id="report-data" type="application/octet-stream">SUPERCOV_REPORT_PAYLOAD</script>
<script>
(() => {
"use strict";
const app = document.getElementById("app");
const payload = document.getElementById("report-data").textContent.trim();
const state = { bundle: null, itemId: null, runId: null, compareId: null, tab: "review", file: null, line: null, gapQuery: "", gapFilter: "all", testQuery: "", testSuite: null, testId: null, testFileFilter: null, testKind: "all", sourceRadius: 32, testFileLimit: 12, assertLine: null, sourceWindow: null, scrollToLine: null, fileSort: "coverage", fileSortDescending: false, fileKind: null, qualityFiles: false, overviewSort: null, overviewDescending: false, overviewQualitySort: "quality", overviewQualityDescending: false };
const tabs = ["review", "explore", "tests"];
const tabLabels = { review: "Overview", explore: "Files", tests: "Tests" };
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)");
function node(tag, className, text) {
const value = document.createElement(tag);
if (className) value.className = className;
if (text !== undefined && text !== null) value.textContent = String(text);
return value;
}
function button(text, className, action) {
const value = node("button", className, text);
value.type = "button";
value.addEventListener("click", action);
return value;
}
function brandMark() {
const namespace = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(namespace, "svg");
svg.setAttribute("class", "mark");
svg.setAttribute("viewBox", "0 0 256 256");
svg.setAttribute("aria-hidden", "true");
[
"M44 100h168a28 28 0 0 1 0 56H44a28 28 0 0 1 0-56Z",
"M128 16a28 28 0 0 1 28 28v168a28 28 0 0 1-56 0V44a28 28 0 0 1 28-28Z",
"M48.804 88.402 167.598 207.196A28 28 0 0 0 207.196 167.598L88.402 48.804A28 28 0 0 0 48.804 88.402Z",
"M88.402 207.196 207.196 88.402A28 28 0 0 0 167.598 48.804L48.804 167.598A28 28 0 0 0 88.402 207.196Z"
].forEach(value => {
const path = document.createElementNS(namespace, "path");
path.setAttribute("d", value);
path.setAttribute("fill", "currentColor");
svg.append(path);
});
return svg;
}
// One decimal at most, and none when the number is whole: 100% and 6.1%,
// never 100.00%. Two decimals promised a precision no reader acts on.
function pct(value) { if (!Number.isFinite(Number(value))) return "\u2014"; const rounded = Math.round(Number(value) * 10) / 10; return `${Number.isInteger(rounded) ? rounded : rounded.toFixed(1)}%`; }
function duration(ms) { const seconds = Number(ms) / 1000; if (!Number.isFinite(seconds)) return "\u2014"; if (seconds < 60) return `${seconds < 10 ? seconds.toFixed(1) : Math.round(seconds)}s`; const minutes = Math.floor(seconds / 60); return `${minutes}m ${Math.round(seconds - minutes * 60)}s`; }
function longDate(value) { try { return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(new Date(value)); } catch { return value; } }
function integer(value) { return new Intl.NumberFormat().format(Number(value) || 0); }
function shortDate(value) { try { return new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }).format(new Date(value)); } catch { return value; } }
function currentItem() { return state.bundle.timeline.find(item => item.id === state.itemId) || state.bundle.timeline[0]; }
function currentRun() { const item = currentItem(); return (item && item.runId && state.bundle.runs.find(run => run.id === item.runId)) || null; }
function currentQuality() { const item = currentItem(); return (item && item.qualityId && state.bundle.qualities.find(value => value.id === item.qualityId)) || null; }
function qualityRow(quality, path) { return quality && Array.isArray(quality.files) ? quality.files.find(file => file.path === path) || null : null; }
// Banded on the number a reader sees, exactly as the command line bands it,
// so the same assessment never reads as one grade here and another there.
function band(health) {
if (!Number.isFinite(Number(health))) return "\u2014";
const rounded = Math.round(Number(health) * 10) / 10;
return rounded >= 8 ? "good" : rounded >= 5 ? "fair" : "weak";
}
function health(value) { return Number.isFinite(Number(value)) ? `${(Math.round(Number(value) * 10) / 10).toFixed(1)}/10` : "\u2014"; }
function compareRun() { return state.bundle.runs.find(run => run.id === state.compareId) || null; }
function missing(file) { return (file.uncoveredLines || 0) + (file.uncoveredStatements || 0) + (file.uncoveredFunctions || 0) + (file.missingBranches || 0) + (file.missingMcdcConditions || 0); }
function gapFiles(run) { return run.files.filter(file => missing(file) > 0 || file.measurementLimitations > 0); }
function fileGapSummary(file) {
return [[file.uncoveredLines, "line"], [file.missingBranches, "branch"], [file.missingMcdcConditions, "decision"], [file.uncoveredFunctions, "function"], [file.uncoveredStatements, "statement"], [file.measurementLimitations, "measurement limit"]]
.filter(([count]) => count)
.map(([count, label]) => `${integer(count)} ${label}${count === 1 ? "" : "s"}`)
.join(" · ");
}
function fileName(value) { return value.split("/").pop() || value; }
function fileDirectory(value) { const parts = value.split("/"); parts.pop(); return parts.join("/"); }
function filePath(value, className = "file-row-name", tag = "span") {
const label = node(tag, `${className} file-path`); label.title = value;
const directory = fileDirectory(value);
if (directory) label.append(node("span", "path-directory", directory + "/"));
label.append(node("strong", "path-basename", fileName(value))); return label;
}
function concernValue(value, className = "concern-value") {
const valid = value !== null && value !== undefined && Number.isFinite(Number(value));
const label = node("span", className, valid ? Number(value).toFixed(2) : "—");
label.title = valid ? `Recorded property value: ${Number(value).toFixed(2)} of 1` : "No property value recorded";
if (valid) label.style.setProperty("--concern-hue", String(42 * (1 - Math.max(0, Math.min(1, Number(value))))));
return label;
}
function concernPill(check, value) {
const paths = {
god_class: "M3 3h6v6H3zM15 3h6v6h-6zM3 15h6v6H3zM15 15h6v6h-6zM9 6h6M6 9v6M18 9v6M9 18h6",
long_method: "M8 4h12M8 8h12M8 12h12M8 16h12M8 20h12M3 4v16M2 4h2M2 20h2",
deep_nesting: "M3 4h6v5h6v5h6v6M3 9v11h13",
complex_conditional: "M12 3v5M12 8L5 14M12 8l7 6M5 14v6M19 14v6M2 20h6M16 20h6",
long_parameter_list: "M4 4H2v16h2M20 4h2v16h-2M7 8v8M12 8v8M17 8v8",
duplicated_logic: "M8 8h12v12H8zM16 8V4H4v12h4",
primitive_obsession: "M4 6h16v12H4zM8 10l-2 2 2 2M16 10l2 2-2 2M13 9l-2 6",
dead_code: "M6 7l-4 5 4 5M18 7l4 5-4 5M15 3L9 21",
feature_envy: "M3 5h7v14H3zM16 5h5v14h-5M8 12h10M14 9l4 3-4 3",
temporary_field: "M4 3h16M4 21h16M6 3v4l6 5-6 5v4M18 3v4l-6 5 6 5v4",
message_chains: "M8 8l3-3a4 4 0 016 6l-3 3M16 16l-3 3a4 4 0 01-6-6l3-3M8 16l8-8",
magic_values: "M9 3L7 21M17 3l-2 18M3 9h18M2 15h18"
};
const pill = node("span", "concern-pill");
const icon = svgMark(`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="${paths[check] || "M12 3l9 9-9 9-9-9z"}"/></svg>`);
icon.setAttribute("aria-hidden", "true");
const label = check.replace(/_/g, " "); pill.append(icon, node("span", "", label.charAt(0).toUpperCase() + label.slice(1)));
if (value !== null && value !== undefined && Number.isFinite(Number(value))) {
pill.style.setProperty("--concern-hue", String(42 * (1 - Math.max(0, Math.min(1, Number(value))))));
pill.classList.add("scored"); pill.title = `Recorded property value: ${Number(value).toFixed(2)} of 1`;
}
return pill;
}
function overviewSortButton(key, label, activeKey, descending, onSort, className = "") {
const active = key === activeKey;
const control = button(`${label}${active ? descending ? " ↓" : " ↑" : ""}`, `overview-sort ${className}${active ? " active" : ""}`, () => onSort(key, active ? !descending : false));
control.setAttribute("aria-label", `${label}${active ? `, sorted ${descending ? "descending" : "ascending"}` : ""}. Sort ${active && !descending ? "descending" : "ascending"}`);
return control;
}
function summaryMetric(run, key) { return run.summary.coverage[key] || { covered: 0, total: 0, percentage: 0 }; }
function mcdc(run) { return { covered: run.summary.coverage.coveredConditions || 0, total: run.summary.coverage.conditions || 0, percentage: run.summary.coverage.conditionCoveragePct || 0 }; }
// ---------- marks ----------
// Built as markup and parsed once, so every mark in the report comes from one
// definition and cannot drift between the overview, a file row and a header.
function svgMark(markup, label) {
const box = node("span", "jmark"); box.innerHTML = markup;
if (label) { box.title = label; box.setAttribute("role", "img"); box.setAttribute("aria-label", label); }
return box;
}
const MARK_R = 15.5, MARK_C = 2 * Math.PI * 15.5, CREDIT_SWEEP = .72;
// Coverage: a ring that closes, because this is the one judgment with a target.
// Under 40px a missing sliver can be thinner than a pixel, so the mark drops the
// arc and carries state alone rather than reading "closed" beside "8 left".
function markCoverage(percentage, size, open) {
if (percentage == null || !Number.isFinite(Number(percentage))) return svgMark(`<svg width="${size}" height="${size}" viewBox="0 0 36 36"><circle class="track-quiet" cx="18" cy="18" r="15.5" fill="none" stroke-width="3"/></svg>`, "Coverage: not measured");
const width = size <= 20 ? 3 : size <= 30 ? 2.6 : 3.2;
const label = open ? `Coverage: ${integer(open)} ${open === 1 ? "obligation" : "obligations"} left to reach` : "Coverage: every obligation reached";
if (size < 40) return svgMark(`<svg width="${size}" height="${size}" viewBox="0 0 36 36"><circle class="state-${open ? "open" : "clear"}" cx="18" cy="18" r="${MARK_R}" fill="none" stroke-width="${width}"/></svg>`, label);
const done = MARK_C * Math.max(0, Math.min(100, percentage)) / 100;
return svgMark(`<svg width="${size}" height="${size}" viewBox="0 0 36 36">`
+ `<circle class="track-${open ? "open" : "quiet"}" cx="18" cy="18" r="${MARK_R}" fill="none" stroke-width="${width}"${open ? "" : ' opacity=".35"'}/>`
+ `<circle class="fill-cov" cx="18" cy="18" r="${MARK_R}" fill="none" stroke-width="${width}" stroke-linecap="butt" stroke-dasharray="${done.toFixed(2)} ${(MARK_C - done).toFixed(2)}" transform="rotate(-90 18 18)"/></svg>`, label);
}
// Credit: an arc with a permanent gap at the bottom. The gap is the point — how
// complete the map could be is unknown, so the shape must not promise a circle.
// Colour deepens with the number but stays one hue: a second colour would
// announce a threshold, and credit has none to cross.
function markCredit(percentage, size) {
if (percentage == null || !Number.isFinite(Number(percentage))) return svgMark(`<svg width="${size}" height="${size}" viewBox="0 0 36 36"><path class="track-quiet" d="M6 29 A15.5 15.5 0 1 1 30 29" fill="none" stroke-width="3" stroke-linecap="round"/></svg>`, "Assertions: not measured");
const width = size <= 20 ? 3 : size <= 30 ? 2.6 : 3.2;
const share = Math.max(0, Math.min(100, percentage));
const arc = MARK_C * CREDIT_SWEEP, done = arc * share / 100;
const start = 90 + (360 * (1 - CREDIT_SWEEP)) / 2;
const ink = `color-mix(in srgb, var(--accent) ${(46 + 54 * share / 100).toFixed(0)}%, var(--faint))`;
return svgMark(`<svg width="${size}" height="${size}" viewBox="0 0 36 36">`
+ `<circle class="track-quiet" cx="18" cy="18" r="${MARK_R}" fill="none" stroke-width="${width}" stroke-linecap="round" stroke-dasharray="${arc.toFixed(2)} ${(MARK_C - arc).toFixed(2)}" transform="rotate(${start} 18 18)"/>`
+ `<circle cx="18" cy="18" r="${MARK_R}" fill="none" stroke-width="${width}" stroke-linecap="round" style="stroke:${ink}" stroke-dasharray="${done.toFixed(2)} ${(MARK_C - done).toFixed(2)}" transform="rotate(${start} 18 18)"/></svg>`,
`Assertion credit: ${pct(share)} of measured statements`);
}
// A pointer locates the quality score on its three colored bands.
function markQuality(value, size) {
const at = (u, radius = 16) => { const angle = Math.PI * (1 - u); return [(20 + radius * Math.cos(angle)).toFixed(2), (21 - radius * Math.sin(angle)).toFixed(2)]; };
const arc = (start, end, tone) => { const from = at(start), to = at(end); return `<path class="quality-scale-${tone}" d="M${from[0]} ${from[1]} A16 16 0 0 1 ${to[0]} ${to[1]}" fill="none" stroke-width="3.5" stroke-linecap="round"/>`; };
const box = `<svg width="${size}" height="${Math.round(size * .62)}" viewBox="0 0 40 25">`;
if (value === null || value === undefined || !Number.isFinite(Number(value))) return svgMark(`${box}${arc(0, 1, "empty")}</svg>`, "Quality: not assessed");
const share = Math.max(0, Math.min(1, Number(value) / 10));
const inner = at(share, 11.5), outer = at(share, 19);
const pointer = `M${inner[0]} ${inner[1]}L${outer[0]} ${outer[1]}`;
return svgMark(`${box}${arc(0, .48, "weak")}${arc(.52, .78, "fair")}${arc(.82, 1, "good")}<path class="quality-pointer-halo" d="${pointer}" stroke-width="4" stroke-linecap="round"/><path class="quality-pointer" d="${pointer}" stroke-width="1.8" stroke-linecap="round"/></svg>`, `Quality: ${health(value)}, the ${band(value)} band. Red below 5, amber from 5 to below 8, green from 8 to 10. A ranking, not a target.`);
}
// The same two marks the source gutter uses, so a bang means one thing everywhere.
function markState(open, size) {
const dimension = size || 15;
if (!open) return svgMark(`<svg width="${dimension}" height="${dimension}" viewBox="0 0 16 16"><path class="ic-reached" d="M3.2 8.6 6.4 11.8 12.8 4.6" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`, "all reached");
return svgMark(`<svg width="${dimension}" height="${dimension}" viewBox="0 0 16 16"><path class="ic-left" d="M8 3.1v5.6" fill="none" stroke-width="2.1" stroke-linecap="round"/><circle class="ic-core" cx="8" cy="12.2" r="1.15"/></svg>`, `${integer(open)} left to reach`);
}
// ---------- the obligation pool ----------
// Coverage is counted, never averaged. The engine's own definition of a missing
// obligation is uncoveredLines + uncoveredStatements + uncoveredFunctions +
// missingBranches + missingMcdcConditions, so the ring and the headline divide
// the same five families and can never disagree.
const OBLIGATION_KINDS = [
{ id: "lines", label: "Lines", one: "line", many: "lines", field: "uncoveredLines", why: "Executable source lines a test drove." },
{ id: "statements", label: "Statements", one: "statement", many: "statements", field: "uncoveredStatements", why: "Whole statements executed, including the multi-line ones." },
{ id: "branches", label: "Branches", one: "branch", many: "branches", field: "missingBranches", why: "Decision outcomes and alternate paths exercised by a test." },
{ id: "mcdc", label: "MC/DC", one: "MC/DC condition", many: "MC/DC conditions", field: "missingMcdcConditions", why: "Conditions shown to independently change their decision." },
{ id: "functions", label: "Functions", one: "function", many: "functions", field: "uncoveredFunctions", why: "Functions entered at least once." }
];
function obligationPool(run) {
const coverage = run.summary.coverage;
const totals = { lines: coverage.lines, statements: coverage.statements, branches: coverage.branches, functions: coverage.functions, mcdc: mcdc(run) };
const pool = { kinds: {}, covered: 0, total: 0, open: 0 };
OBLIGATION_KINDS.forEach(kind => {
const total = Number((totals[kind.id] || {}).total) || 0;
const open = run.files.reduce((sum, file) => sum + (Number(file[kind.field]) || 0), 0);
const covered = Math.max(0, total - open);
pool.kinds[kind.id] = { covered, total, open, percentage: total ? covered / total * 100 : 100 };
pool.covered += covered; pool.total += total; pool.open += open;
});
pool.percentage = pool.total ? pool.covered / pool.total * 100 : 100;
return pool;
}
function coverageBreakdownPool(run) {
const kinds = {};
OBLIGATION_KINDS.forEach(kind => {
const metric = kind.id === "mcdc" ? mcdc(run) : summaryMetric(run, kind.id);
kinds[kind.id] = { ...metric, open: metric.total - metric.covered };
});
return { kinds };
}
function fileObligationPool(run, file) {
const record = run?.files.find(value => value.file === file);
const totals = { ...(run?.fileTotals?.[file] || {}) };
totals.lines = (run?.lines || []).filter(line => line.file === file).length;
const decisions = (run?.decisions || []).filter(decision => decision.meta.file === file);
totals.mcdc = decisions.reduce((total, decision) => total + decision.conditions.length, 0);
const pool = { kinds: {} };
OBLIGATION_KINDS.forEach(kind => {
const total = totals[kind.id] || 0;
let open = Number(record?.[kind.field]) || 0;
pool.kinds[kind.id] = { total, open, percentage: total ? Math.max(0, total - open) / total * 100 : null };
});
return pool;
}
function filesOwing(run, kindId) {
const kind = OBLIGATION_KINDS.find(entry => entry.id === kindId);
return kind ? run.files.filter(file => (Number(file[kind.field]) || 0) > 0) : [];
}
// The address names a place — tab, timeline entry, file, line — and gets a
// history entry when that place changes, so the browser's back button
// walks the report the way a reader walked it. Typing in a search box
// changes no place and writes nothing.
let restoringHash = false;
function setHash() {
const parts = [state.tab, state.itemId];
if (state.file) parts.push(encodeURIComponent(state.file));
if (state.line) parts.push(String(state.line));
const next = `#${parts.join("/")}`;
if (restoringHash || next === location.hash) return;
if (location.hash) history.pushState(null, "", next); else history.replaceState(null, "", next);
}
function readHash() {
const parts = location.hash.slice(1).split("/");
if (tabs.includes(parts[0])) state.tab = parts[0];
if (state.bundle.timeline.some(item => item.id === parts[1])) state.itemId = parts[1];
const item = state.bundle.timeline.find(value => value.id === state.itemId) || state.bundle.timeline[0];
state.itemId = item ? item.id : null;
state.runId = (item && item.runId) || null;
state.compareId = state.runId ? previousRunId(state.runId) : null;
state.file = null; state.line = null;
if (parts[2]) { try { state.file = decodeURIComponent(parts[2]); } catch {} }
if (parts[3] && /^\d+$/.test(parts[3])) state.line = Number(parts[3]);
state.assertLine = null; state.sourceWindow = null; state.testId = null; state.testSuite = null; state.testFileFilter = null;
}
function previousRunId(runId) {
const index = state.bundle.runs.findIndex(run => run.id === runId);
return index >= 0 ? state.bundle.runs[index + 1]?.id || null : null;
}
function chooseItem(itemId) {
state.qualityFiles = false; state.overviewSort = null; state.overviewDescending = false; state.overviewQualitySort = "quality"; state.overviewQualityDescending = false;
state.itemId = itemId;
const item = state.bundle.timeline.find(value => value.id === itemId);
state.runId = (item && item.runId) || null;
state.compareId = state.runId ? previousRunId(state.runId) : null;
state.file = null; state.line = null; state.testSuite = null; state.testId = null; state.testFileFilter = null; state.testKind = "all"; state.sourceRadius = 32; state.testFileLimit = 12;
render();
}
let storedTheme = null;
function effectiveTheme() { return storedTheme || (systemTheme.matches ? "dark" : "light"); }
function applyTheme() { document.documentElement.dataset.theme = effectiveTheme(); }
function syncThemeUi() {
const dark = effectiveTheme() === "dark";
document.querySelector('meta[name="theme-color"]')?.setAttribute("content", dark ? "#0f1218" : "#f2f1ed");
document.querySelectorAll(".theme-toggle").forEach(value => {
const label = `Switch to ${dark ? "light" : "dark"} mode`;
value.setAttribute("aria-label", label); value.setAttribute("title", label);
});
}
function themeToggle() {
const namespace = "http://www.w3.org/2000/svg";
const value = button("", "theme-toggle", () => {
storedTheme = effectiveTheme() === "dark" ? "light" : "dark";
try { localStorage.setItem("supercov-report-theme", storedTheme); } catch {}
applyTheme(); syncThemeUi();
});
const moon = document.createElementNS(namespace, "svg");
moon.setAttribute("class", "theme-icon theme-icon-moon"); moon.setAttribute("viewBox", "0 0 24 24"); moon.setAttribute("aria-hidden", "true");
const moonPath = document.createElementNS(namespace, "path"); moonPath.setAttribute("d", "M20.2 15.1A8.4 8.4 0 0 1 8.9 3.8 8.5 8.5 0 1 0 20.2 15.1Z"); moon.append(moonPath);
const sun = document.createElementNS(namespace, "svg");
sun.setAttribute("class", "theme-icon theme-icon-sun"); sun.setAttribute("viewBox", "0 0 24 24"); sun.setAttribute("aria-hidden", "true");
const circle = document.createElementNS(namespace, "circle"); circle.setAttribute("cx", "12"); circle.setAttribute("cy", "12"); circle.setAttribute("r", "3.7"); sun.append(circle);
["M12 2v2.1", "M12 19.9V22", "M4.9 4.9l1.5 1.5", "M17.6 17.6l1.5 1.5", "M2 12h2.1", "M19.9 12H22", "M4.9 19.1l1.5-1.5", "M17.6 6.4l1.5-1.5"].forEach(pathValue => {
const path = document.createElementNS(namespace, "path"); path.setAttribute("d", pathValue); sun.append(path);
});
value.append(moon, sun); return value;
}
function runTone(run) {
if (!run.summary.valid) return "bad";
if (!run.summary.measurement.complete || allGapKeys(run).size > 0) return "warn";
return "good";
}
function renderSidebar(layout) {
const aside = node("aside", "run-sidebar");
const brand = node("div", "sidebar-brand"); brand.append(brandMark(), node("span", "", "Supercov"), themeToggle()); aside.append(brand);
aside.append(node("div", "sidebar-project", "Project"));
const project = node("div", "sidebar-project-name", state.bundle.project); project.title = state.bundle.project; aside.append(project);
const history = node("div", "run-history"); const list = node("div", "run-list");
const selected = currentItem();
state.bundle.timeline.forEach(item => {
const itemRun = item.runId ? state.bundle.runs.find(value => value.id === item.runId) : null;
const itemQuality = item.qualityId ? state.bundle.qualities.find(value => value.id === item.qualityId) : null;
const current = selected && item.id === selected.id;
const value = button("", "run-item", () => chooseItem(item.id)); value.setAttribute("aria-current", String(current));
value.append(node("span", "run-date", shortDate(item.at)));
const metrics = node("span", "run-metrics");
const metric = (label, chart, score, description = null) => {
const entry = node("span", "run-metric"); entry.title = `${label}: ${description || (score === "—" ? "Not assessed" : score)}`;
entry.setAttribute("aria-label", entry.title); chart.setAttribute("aria-hidden", "true");
entry.append(chart, node("span", "", score)); metrics.append(entry); return entry;
};
const pool = itemRun && obligationPool(itemRun);
metric("Coverage", markCoverage(pool?.total ? pool.percentage : null, 18, pool?.open), pool?.total ? pct(pool.percentage) : "—");
const assertions = itemRun && assertionData(itemRun)?.summary;
const asserted = assertions?.status === "available" ? assertions.statements?.percentage : null;
metric("Assertions", markCredit(asserted, 18), asserted != null ? pct(asserted) : "—");
metric("Quality", markQuality(itemQuality?.health, 20), itemQuality ? health(itemQuality.health) : "—");
const audit = (state.bundle.securities || []).find(value => value.id === item.securityId);
const security = securityInfo(null, audit || null);
const securityStatus = security.assessed.length ? `${integer(security.flagged.length)} ${security.flagged.length === 1 ? "file" : "files"} flagged` : security.errors.length ? "Assessment failed" : "Not assessed";
const securityMetric = metric("Security", markSecurity(18, securityStatus), security.assessed.length ? integer(security.flagged.length) : "—", securityStatus);
if (security.flagged.length) securityMetric.classList.add("security-flagged");
value.append(metrics); list.append(value);
});
history.append(list); aside.append(history);
layout.append(aside);
}
function primaryCoverage(run) {
const decision = mcdc(run);
if (decision.total > 0) return { ...decision, label: "MC/DC coverage" };
const branches = summaryMetric(run, "branches");
if (branches.total > 0) return { ...branches, label: "Branch coverage" };
return { ...summaryMetric(run, "lines"), label: "Line coverage" };
}
function coverageChange(run) {
const other = compareRun();
if (!other) return null;
const now = allGapKeys(run), before = allGapKeys(other);
let closed = 0, opened = 0;
before.forEach(key => { if (!now.has(key)) closed++; });
now.forEach(key => { if (!before.has(key)) opened++; });
if (opened > 0) return { tone: "bad", text: `${integer(opened)} new ${opened === 1 ? "gap" : "gaps"}` };
if (closed > 0) return { tone: "good", text: `${integer(closed)} ${closed === 1 ? "gap" : "gaps"} closed` };
const difference = primaryCoverage(run).percentage - primaryCoverage(other).percentage;
if (Math.abs(difference) < .005) return null;
const text = `${difference > 0 ? "+" : ""}${difference.toFixed(2)} pp`;
if (Math.abs(difference) < .05) return { tone: "quiet", text, title: "Small coverage movement" };
return { tone: difference > 0 ? "good" : "bad", text };
}
function closedGapsByFile(run) {
const other = compareRun(); const result = new Map();
if (!other) return result;
const current = new Map(); Object.entries(run.fileDetails).forEach(([file, detail]) => current.set(file, new Set(detail.gapLines.flatMap(line => line.obligations.map(obligation => obligationKey(file, line.line, obligation))))));
Object.entries(other.fileDetails).forEach(([file, detail]) => {
const now = current.get(file) || new Set(); let closed = 0;
detail.gapLines.forEach(line => line.obligations.forEach(obligation => { if (!now.has(obligationKey(file, line.line, obligation))) closed++; }));
if (closed) result.set(file, closed);
});
return result;
}
function renderLedger(run) {
const visual = node("div", "coverage-visual");
const map = node("div", "ledger");
const openFiles = gapFiles(run).length;
const improvedFiles = closedGapsByFile(run);
map.setAttribute("role", "group");
map.setAttribute("aria-label", `${integer(run.files.length)} measured files, ${integer(openFiles)} with obligations left to reach or measurement limits`);
run.files.forEach(file => {
const isOpen = missing(file) > 0;
const isLimited = file.measurementLimitations > 0;
const closed = improvedFiles.get(file.file) || 0;
const mark = button("", `file-mark${isLimited ? " limited" : isOpen ? " open" : " covered"}${closed ? " improved" : ""}`, () => { state.tab = "explore"; state.file = file.file; state.line = null; state.sourceRadius = 32; render(); });
const current = isOpen || isLimited ? fileGapSummary(file) : "covered"; const improvement = closed ? `; ${integer(closed)} ${closed === 1 ? "gap" : "gaps"} closed in this run` : "";
mark.setAttribute("aria-label", `${file.file}: ${current}${improvement}`);
mark.title = `${file.file} — ${current}${improvement}`;
map.append(mark);
});
const legend = node("div", "ledger-legend");
const limited = run.files.filter(file => file.measurementLimitations > 0).length;
const key = (tone, label) => { const item = node("span", tone); item.append(node("i"), document.createTextNode(label)); return item; };
legend.append(key("", `${integer(run.files.length - openFiles)} covered`), key("o", `${integer(openFiles - limited)} with work left`));
if (limited) legend.append(key("l", `${integer(limited)} measurement-limited`));
if (improvedFiles.size) legend.append(key("", `${integer(improvedFiles.size)} improved since the previous run`));
visual.append(map, legend); return visual;
}
// Recorded source identity stays quiet; the measured command opens on demand.
function renderRecord(run) {
const record = node("section", "record"); record.setAttribute("aria-label", "Run context");
const context = node("div", "record-context");
if (run.git) {
if (run.git.branch) context.append(node("code", "git-branch", run.git.branch));
if (run.git.revision) {
const revision = node("code", "git-revision", `${run.git.dirty ? "base " : ""}${run.git.revision.slice(0, 7)}`);
revision.title = run.git.revision; context.append(revision);
}
context.append(node("span", "git-state", run.git.dirty ? "Uncommitted changes" : "No local changes"));
}
context.append(node("span", "", longDate(run.startedAt)));
record.append(context);
const command = button("", "record-command", () => openReportModal("command", run, command));
command.setAttribute("aria-label", "Test command"); command.setAttribute("aria-haspopup", "dialog");
const icon = svgMark('<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"><path d="m3 4 4 4-4 4m6 0h4"/></svg>'); icon.setAttribute("aria-hidden", "true");
command.append(icon, node("span", "record-command-label", "Test command")); context.append(command);
return record;
}
function renderCoverageInstrument(run) {
const metric = node("div", "overview-metric");
const heading = node("div", "metric-heading"); heading.append(node("h2", "overview-label", "Coverage")); metric.append(heading);
const pool = obligationPool(run);
const head = node("div", "instrument-head");
head.append(markCoverage(pool.total ? pool.percentage : null, 62, pool.open));
const stack = node("div", "figure-stack");
const figure = node("div", "figure"); figure.append(node("span", "figure-number", pool.total ? pct(pool.percentage) : "—"));
stack.append(figure, node("div", "figure-unit", pool.total ? "covered by tests" : "No coverage measured"));
const breakdown = button("View breakdown", "subtle-button coverage-breakdown", () => openReportModal("coverage", run, breakdown));
breakdown.setAttribute("aria-haspopup", "dialog");
const actions = node("div", "metric-actions"); actions.append(breakdown); if (!pool.total || pool.open) actions.append(improveButton("coverage")); heading.append(actions); head.append(stack); metric.append(head);
return metric;
}
// Each kind is a facet of the one pool, so picking one does not re-scope this
// card: it opens the files that owe that kind. A kind with nothing left is not
// a link, because there is nowhere to go.
function renderObligations(run, pool, file = null) {
const list = node("div", "kind-rows");
OBLIGATION_KINDS.forEach(kind => {
const metric = pool.kinds[kind.id];
const navigable = !file && metric.open > 0 && filesOwing(run, kind.id).length > 0;
const row = navigable
? button("", "kind-row go", () => { state.tab = "explore"; state.file = null; state.fileKind = kind.id; state.gapQuery = ""; render(); })
: node("div", "kind-row");
row.title = kind.why;
row.append(node("span", "kind-name", kind.label));
const share = node("span", "kind-share"); const chart = markCoverage(metric.total ? metric.percentage : null, 40, metric.open);
chart.setAttribute("aria-hidden", "true"); share.append(chart, node("span", "", metric.total ? pct(metric.percentage) : "—")); row.append(share);
const go = node("span", "kind-go", navigable ? "›" : ""); go.setAttribute("aria-hidden", "true"); row.append(go);
row.setAttribute("aria-label", `${kind.label}: ${metric.total ? `${pct(metric.percentage)} covered` : "Not measured"}${navigable ? ". View files with gaps" : ""}`);
list.append(row);
});
return list;
}
// Quality reads as a band and a ranking, never as progress toward a target:
// the assessment records that it has no cutoffs and fails on no finding, so
// a bar beside 100% coverage would invent a goal that does not exist.
function renderQuality(quality) {
const card = node("section", "instrument overview-card"); card.setAttribute("aria-label", "Quality");
const heading = node("div", "metric-heading"); heading.append(node("h2", "overview-label", "Quality")); if (quality.health == null || Number(quality.health) < 10) heading.append(improveButton("quality")); card.append(heading);
const head = node("div", "instrument-head"); head.append(markQuality(quality.health, 62));
const stack = node("div", "figure-stack");
const figure = node("div", "figure");
figure.append(node("span", "figure-number", (Math.round(Number(quality.health) * 10) / 10).toFixed(1)), node("span", "figure-unit", "/10"), node("span", `pill quality-rating ${band(quality.health)}`, band(quality.health)));
const files = (Array.isArray(quality.files) ? quality.files : []).filter(file => Number.isFinite(Number(file.health)));
const counts = { good: 0, fair: 0, weak: 0 }; files.forEach(file => counts[band(file.health)]++);
stack.append(figure, node("div", "figure-sub", `${integer(files.length)} files assessed · ${integer(counts.good)} good · ${integer(counts.fair)} fair · ${integer(counts.weak)} weak`));
head.append(stack); card.append(head);
if (files.length) {
const list = node("div", "overview-files");
const columns = node("div", "overview-row overview-quality-row overview-row-head");
const sort = (key, descending) => { state.overviewQualitySort = key; state.overviewQualityDescending = descending; render(); };
columns.append(overviewSortButton("name", "File", state.overviewQualitySort, state.overviewQualityDescending, sort, "overview-file-label"), node("span", "overview-concerns", "Main concerns"), overviewSortButton("quality", "Quality", state.overviewQualitySort, state.overviewQualityDescending, sort)); list.append(columns);
files.slice().sort((a, b) => (state.overviewQualitySort === "name" ? a.path.localeCompare(b.path) : Number(a.health) - Number(b.health)) * (state.overviewQualityDescending ? -1 : 1) || a.path.localeCompare(b.path)).slice(0, 3).forEach(file => {
const row = button("", "overview-row overview-quality-row", () => openOverviewFile(file.path, true));
row.append(node("span", `file-state-dot ${band(file.health)}`), overviewFileIdentity(file.path));
const concerns = node("span", "overview-concerns concern-pills");
(file.present || []).slice(0, 2).forEach(check => concerns.append(concernPill(check.check, check.value)));
if (!concerns.childNodes.length) concerns.append(node("span", "", "No concerns recorded")); row.append(concerns);
const score = node("span", "file-row-mark"); score.append(markQuality(file.health, 26), node("span", "", health(file.health))); row.append(score, node("span", "file-chevron", "›")); labelFileMetrics(row, ["Quality"]); list.append(row);
});
card.append(list);
}
const footer = node("div", "overview-footer"); footer.append(button("All assessed files ›", "link", () => { state.tab = "explore"; state.file = null; state.fileKind = null; state.gapQuery = ""; state.fileSort = state.overviewQualitySort; state.fileSortDescending = state.overviewQualityDescending; state.qualityFiles = true; render(); })); card.append(footer);
return card;
}
// Only "available" carries a percentage. The other states describe why there
// is no number, and printing one anyway would be the lie the command line
// already refuses to tell.
function renderAssertions(run) {
const metric = node("div", "overview-metric");
const heading = node("div", "metric-heading"); heading.append(node("h2", "overview-label", "Assertions")); metric.append(heading);
const data = run.assertions;
const summary = data && data.summary || {};
if (!data || !data.available || summary.status !== "available" || summary.statements?.percentage == null) return missingMetric("assertions", "Assertions", data?.error || "Not assessed for this run. Run: npx supercov runs latest assertions assess");
const statements = summary.statements || {};
if (statements.percentage == null || Number(statements.percentage) < 100) heading.append(improveButton("assertions"));
const head = node("div", "instrument-head"); head.append(markCredit(Number(statements.percentage) || 0, 62));
const stack = node("div", "figure-stack");
const figure = node("div", "figure"); figure.append(node("span", "figure-number", pct(statements.percentage)));
stack.append(figure, node("div", "figure-unit", "asserted"));
head.append(stack); metric.append(head);
return metric;
}
function openOverviewFile(file, assessed = false) {
state.tab = "explore"; state.file = file; state.line = null; state.assertLine = null; state.sourceRadius = 32; state.qualityFiles = assessed; render();
}
function labelFileMetrics(row, labels = ["Coverage", "Assertions", "Quality", "Security"]) {
row.querySelectorAll(".file-row-mark").forEach((metric, index) => {
metric.prepend(node("span", "mobile-metric-label", labels[index]));
});
}
function overviewFileIdentity(file) {
const identity = node("span", "file-row-identity"); identity.title = file;
identity.append(filePath(file));
return identity;
}
function renderTestEvidence(run) {
const card = node("section", "instrument overview-card"); card.setAttribute("aria-label", "Coverage and assertions");
const metrics = node("div", "overview-metrics"); metrics.append(renderCoverageInstrument(run), renderAssertions(run)); card.append(metrics);
const tests = (run.tests || []).filter(test => test.role === "test");
const passed = tests.filter(test => testTone(test) === "good").length;
const skipped = tests.filter(test => /skip|ignored|todo|pending/i.test(test.outcome || "")).length;
const failed = tests.filter(test => testTone(test) === "bad").length;
const other = tests.length - passed - skipped - failed;
const status = node("div", "run-test-summary");
if (passed) {
const result = node("span", "test-result"); result.append(node("span", "", "✓"), document.createTextNode(`${integer(passed)} ${passed === 1 ? "test" : "tests"} passed`)); status.append(result);
}
if (failed || !run.summary.valid) status.append(node("span", "test-result bad", failed ? `${integer(failed)} failed` : `Tests failed · exit ${run.testExitCode ?? "?"}`));
if (skipped) status.append(node("span", "", `${integer(skipped)} skipped`));
if (other) status.append(node("span", "test-result warn", `${integer(other)} need review`));
if (!tests.length && run.summary.valid) status.append(node("span", "", "No test results"));
status.append(node("span", "", `${integer(run.files.length)} source files`), node("span", "", duration(run.durationMs))); card.append(status);
if (!run.summary.measurement.complete) card.append(node("p", "overview-notice", `Coverage has ${integer(run.summary.measurement.blocking)} measurement ${run.summary.measurement.blocking === 1 ? "limit" : "limits"}. Review the affected files below.`));
if (run.stale) card.append(node("p", "overview-notice", "Source has changed since this run. Results describe the recorded source."));
const availableCredit = run.assertions?.available && run.assertions.summary?.status === "available";
const creditOf = file => { const count = availableCredit && assertionFileCount(run, file.file); return count && count.total ? count.asserted / count.total : null; };
const gaps = gapFiles(run);
const lineTotals = new Map();
run.lines.forEach(line => { const count = lineTotals.get(line.file) || { covered: 0, total: 0 }; count.total++; if (line.covered) count.covered++; lineTotals.set(line.file, count); });
const coverageOf = file => { const count = lineTotals.get(file.file); return count?.total ? count.covered / count.total : null; };
const key = state.overviewSort || (gaps.length || !availableCredit ? "coverage" : "asserted");
const descending = state.overviewDescending;
const compareMetric = (a, b) => a === null ? (b === null ? 0 : 1) : b === null ? -1 : (descending ? b - a : a - b);
const values = run.files.slice().sort((a, b) => {
let order = key === "name" ? a.file.localeCompare(b.file) * (descending ? -1 : 1) : key === "asserted" ? compareMetric(creditOf(a), creditOf(b)) : compareMetric(coverageOf(a), coverageOf(b)) || (missing(b) + b.measurementLimitations - missing(a) - a.measurementLimitations) * (descending ? -1 : 1);
return order || a.file.localeCompare(b.file);
});
if (values.length) {
const list = node("div", "overview-files");
const columns = node("div", "overview-row overview-row-head");
[["name", "File"], ["coverage", "Coverage"], ["asserted", "Assertions"]].forEach(([column, label]) => {
const active = key === column;
const control = button(`${label}${active ? descending ? " ↓" : " ↑" : ""}`, `overview-sort${column === "name" ? " overview-file-label" : ""}${active ? " active" : ""}`, () => {
state.overviewDescending = active ? !descending : false; state.overviewSort = column; render();
});
control.setAttribute("aria-label", `${label}${active ? `, sorted ${descending ? "descending" : "ascending"}` : ""}. Sort ${active && !descending ? "descending" : "ascending"}`);
if (column === "asserted" && !availableCredit) control.disabled = true;
columns.append(control);
});
list.append(columns);
values.slice(0, 3).forEach(file => {
const owed = missing(file), limited = file.measurementLimitations > 0;
const row = button("", "overview-row", () => openOverviewFile(file.file));
row.append(node("span", `file-state-dot${limited ? " limited" : owed ? " open" : ""}`), overviewFileIdentity(file.file));
const lines = run.lines.filter(line => line.file === file.file);
const share = lines.length ? lines.filter(line => line.covered).length / lines.length * 100 : null;
const coverage = node("span", "file-row-mark");
coverage.append(markCoverage(share, 26, owed), node("span", "", limited ? "Limited" : owed ? `${integer(owed)} left` : share === null ? "—" : pct(share)));
coverage.title = limited ? "Coverage measurement is limited" : fileGapSummary(file); row.append(coverage);
const credit = node("span", "file-row-mark"); const fraction = creditOf(file);
if (fraction !== null) credit.append(markCredit(fraction * 100, 26), node("span", "", pct(fraction * 100)));
else credit.append(markCredit(null, 26), node("span", "muted", "—"));
credit.title = fraction !== null ? assertionFileSummary(run, file.file) : "No assertion result available";
row.append(credit, node("span", "file-chevron", "›")); labelFileMetrics(row); list.append(row);
});
card.append(list);
}
const footer = node("div", "overview-footer"); footer.append(button("All files ›", "link", () => { state.tab = "explore"; state.file = null; state.fileKind = null; state.gapQuery = ""; state.qualityFiles = false; state.fileSort = key; state.fileSortDescending = descending; render(); })); card.append(footer);
return card;
}
function currentSecurity() {
const id = currentItem()?.securityId;
return (state.bundle.securities || []).find(value => value.id === id) || null;
}
function securityInfo(file = null, audit = currentSecurity()) {
const files = (audit?.files || []).filter(row => !file || row.path === file);
const assessed = files.filter(row => row.status === "completed");
const paths = (audit?.paths || []).filter(path => !file || path.caller?.file === file || path.callee?.file === file);
const flaggedByPath = new Map(assessed.filter(row => row.present?.length).map(row => [row.path, row]));
for (const path of paths) for (const endpoint of [path.caller, path.callee]) {
if (endpoint?.file && (!file || endpoint.file === file) && !flaggedByPath.has(endpoint.file)) {
flaggedByPath.set(endpoint.file, { path: endpoint.file });
}
}
return { audit, files, assessed, flagged: [...flaggedByPath.values()], paths, errors: files.filter(row => row.status !== "completed") };
}
function securityCount(file) {
const info = securityInfo(file);
return info.assessed.length || info.paths.length
? info.assessed.reduce((sum, row) => sum + (row.present || []).length, 0) + info.paths.length : null;
}
function securityFileMark(file) {
const count = securityCount(file), label = securityLabel(file);
const cell = node("span", `file-row-mark${count === null ? " quiet" : ""}`);
cell.append(markSecurity(22, label), node("span", "", count === null ? "—" : integer(count)));
cell.title = label;
return cell;
}
function securityFilePaths() {
const audit = currentSecurity();
return [...new Set([...(audit?.files || []).map(row => row.path), ...(audit?.paths || []).flatMap(path => [path.caller?.file, path.callee?.file]).filter(Boolean)])];
}
function securityPathEvidence(paths) {
const content = node("div", "security-paths");
for (const path of paths) {
const entry = node("div", "security-finding");
entry.append(node("h4", "annotation-title", (path.check || "Security path").replace(/_/g, " ")));
for (const [label, endpoint] of [["Caller", path.caller], ["Sink", path.callee]]) {
if (!endpoint?.file) continue;
const row = node("p", "evidence-meta");
row.append(node("span", "", `${label}: `));
const link = button("", "security-file-link", () => {
state.tab = "explore"; state.qualityFiles = false; state.file = endpoint.file;
state.line = endpoint.line || null; state.assertLine = null; state.sourceRadius = 32; render();
});
link.append(filePath(endpoint.file), node("span", "", endpoint.line ? `:${endpoint.line}` : ""));
row.append(link);
if (endpoint.function) row.append(node("span", "", ` · ${endpoint.function}`));
entry.append(row);
}
content.append(entry);
}
return content;
}
function appendCrossFileFindings(panel) {
const query = state.gapQuery.toLowerCase();
const paths = (currentSecurity()?.paths || []).filter(path => !query || [path.caller?.file, path.callee?.file].some(file => file?.toLowerCase().includes(query)));
if (!paths.length) return;
const section = node("section", "detail-card cross-file-findings");
section.setAttribute("aria-label", "Cross-file findings");
section.append(node("h2", "overview-label", "Cross-file findings"), node("p", "quality-context", "Each path is counted on the files it connects."), securityPathEvidence(paths));
panel.append(section);
}
function securityLabel(file = null) {
const info = securityInfo(file);
if (!info.assessed.length && !info.paths.length) return info.errors.length ? "Assessment failed" : "Not assessed";
if (file) {
const count = securityCount(file);
return count ? `${integer(count)} ${count === 1 ? "finding" : "findings"}` : "No findings";
}
return `${integer(info.flagged.length)} ${info.flagged.length === 1 ? "file" : "files"} flagged`;
}
function markSecurity(size, label = "Not assessed") {
const mark = svgMark(`<svg width="${size}" height="${size}" viewBox="0 0 28 28"><path d="M14 2 24 6v8c0 6-5 10-10 12C9 24 4 20 4 14V6Z" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>`, `Security: ${label.toLowerCase()}`);
mark.classList.add("security-mark");
if (label === "No findings" || label.startsWith("0 ")) mark.classList.add("clear");
else if (/finding|flagged/.test(label)) mark.classList.add("open");
return mark;
}
function securityEvidence(file = null) {
const info = securityInfo(file), content = node("div", "security-evidence");
for (const row of info.files) {
if (!file) {
const heading = node("h3", "security-file-heading");
const link = button("", "security-file-link", () => openOverviewFile(row.path)); link.append(filePath(row.path)); heading.append(link); content.append(heading);
}
if (row.status !== "completed") { content.append(node("p", "quality-context", row.error || "Assessment failed for this file.")); continue; }
if (!row.present?.length) content.append(node("p", "quality-context", "No file-level findings recorded."));
for (const finding of row.present || []) {
const entry = node("div", "security-finding");
const definition = (info.audit.catalog || []).find(value => (value.id || value.check) === finding.check);
entry.append(node("h4", "annotation-title", finding.check.replace(/_/g, " ")));
entry.append(node("p", "evidence-meta", finding.lines?.length ? "Confirmed at a line" : "File-level finding"));
if (definition?.cwe) entry.append(node("p", "evidence-meta", Array.isArray(definition.cwe) ? definition.cwe.join(" · ") : String(definition.cwe)));
for (const line of finding.lines || []) {
entry.append(node("p", "evidence-meta", `${row.path}:${line.line}`));
if (line.text) entry.append(node("pre", "annotation-assertion", line.text));
if (line.source_line) entry.append(node("p", "evidence-meta", `Input enters at line ${line.source_line}`));
}
content.append(entry);
}
}
if (info.paths.length) content.append(node("h3", "security-file-heading", "Cross-file paths"));
content.append(securityPathEvidence(info.paths));
return content;
}
function renderSecurity(file = false) {
const path = file ? state.file : null, info = securityInfo(path);
const card = node("section", file ? "detail-card file-security" : "instrument overview-card"); card.setAttribute("aria-label", "Security");
const heading = node("div", "metric-heading"); heading.append(node(file ? "h3" : "h2", "overview-label", "Security"), improveButton("security", path)); card.append(heading);
const tone = info.flagged.length ? "flagged" : info.assessed.length && !info.errors.length ? "clear" : "quiet";
const summary = node("div", `security-summary ${tone}`);
const icon = node("span", "security-summary-icon"); icon.append(markSecurity(32, securityLabel(path))); summary.append(icon);
const copy = node("div"); copy.append(node("strong", "", securityLabel(path)));
const context = [`${integer(info.assessed.length)} ${info.assessed.length === 1 ? "file" : "files"} assessed`];
if (info.errors.length) context.push(`${integer(info.errors.length)} failed`);
if (info.paths.length) context.push(`${integer(info.paths.length)} cross-file ${info.paths.length === 1 ? "path" : "paths"}`);
copy.append(node("p", "quality-context", info.audit ? context.join(" · ") : "No security assessment matches this source."));
summary.append(copy); card.append(summary);
if (!info.audit) return card;
if (file) card.append(securityEvidence(path));
else {
const list = node("div", "evidence-list");
for (const row of info.flagged.slice(0, 3)) {
const action = button("", "evidence-row", () => openOverviewFile(row.path));
action.append(filePath(row.path), node("span", "relation-count", securityLabel(row.path)), node("span", "file-chevron", "›")); list.append(action);
}
card.append(list);
const all = button("All files ›", "link", () => { state.tab = "explore"; state.file = null; state.fileKind = null; state.gapQuery = ""; state.qualityFiles = false; state.fileSort = "security"; state.fileSortDescending = true; render(); });
const footer = node("div", "overview-footer"); footer.append(all); card.append(footer);
}
return card;
}
function renderSecurityFiles(content) {
const audit = currentSecurity();
if (state.file) {
const panel = node("section", "detail-view");
panel.append(button("‹ All files", "back-button", () => { state.file = null; render(); }), filePath(state.file, "detail-heading", "h1"), renderFileStats(null, state.file, null));
panel.append(renderPlainSource(audit, state.file), renderSecurity(true)); content.append(panel); return;
}
renderQualityFiles(content, { files: [] });
}
function renderNext(run) {
const section = node("section", "next"); section.setAttribute("aria-label", "Where to look next");
section.append(node("div", "rail", "Next"));
const open = gapFiles(run).slice().sort((a, b) => (missing(b) + b.measurementLimitations) - (missing(a) + a.measurementLimitations) || a.file.localeCompare(b.file));
if (!open.length) {
section.append(node("h2", "next-title", "Nothing left to reach"));
section.append(node("p", "next-copy", "Every measured obligation was observed by a test. What remains is what the tests prove about that code: assertion credit and the quality assessment above say how far that goes."));
section.append(button("Browse files ›", "link", () => { state.tab = "explore"; state.file = null; render(); }));
return section;
}
section.append(node("h2", "next-title", `${integer(open.length)} ${open.length === 1 ? "file needs" : "files need"} a test`));
section.append(node("p", "next-copy", "Ranked by how many obligations are still unobserved. Open one to see the exact line, branch or condition, and the evidence beside it."));
const list = node("div", "next-list");
open.slice(0, 8).forEach(file => {
const limited = file.measurementLimitations > 0;
const row = button("", "file-row", () => { state.tab = "explore"; state.file = file.file; state.line = null; state.sourceRadius = 32; render(); });
row.append(node("span", `file-state-dot${limited ? " limited" : " open"}`));
const identity = node("span", "file-row-identity"); identity.append(filePath(file.file)); identity.append(node("span", "file-row-directory", fileGapSummary(file)));
row.append(identity, node("span", `file-row-metric${limited ? " limited" : " open"}`, limited ? "Limited" : `${integer(missing(file))} open`));
const chevron = node("span", "file-chevron", "›"); chevron.setAttribute("aria-hidden", "true"); row.append(chevron); list.append(row);
});
section.append(list);
section.append(button(open.length > 8 ? `All ${integer(open.length)} files ›` : "Browse files ›", "link", () => { state.tab = "explore"; state.file = null; render(); }));
return section;
}
function renderHero(shell, run) {
const hero = node("section", "hero"); hero.append(renderRecord(run));
const instruments = node("div", "instruments"); instruments.append(renderTestEvidence(run));
const quality = currentQuality();
instruments.append(quality ? renderQuality(quality) : renderMissing("quality", "Not assessed", "No quality assessment matches this source.", true));
instruments.append(renderSecurity()); hero.append(instruments); shell.append(hero);
}
// An assessment with no run against the same source: worth reading on its
// own, and explicit about the half that is absent.
function renderQualityOnly(shell, quality) {
const hero = node("section", "hero");
const instruments = node("div", "instruments");
const card = node("section", "instrument overview-card"); const metrics = node("div", "overview-metrics");
metrics.append(missingMetric("coverage", "Coverage", "Run tests to measure coverage."), missingMetric("assertions", "Assertions", "Assertions coverage needs a coverage run."));
card.append(metrics); instruments.append(card, quality ? renderQuality(quality) : renderMissing("quality", "Not assessed", "No quality assessment matches this source.", true), renderSecurity()); hero.append(instruments); shell.append(hero);
}
function renderTabs(shell) {
const nav = node("nav", "tabs"); nav.setAttribute("aria-label", "Report views"); nav.setAttribute("role", "tablist");
tabs.forEach(name => {
const item = button(tabLabels[name], "tab", () => { state.tab = name; state.qualityFiles = false; state.file = null; state.line = null; state.testSuite = null; state.testId = null; state.testFileFilter = null; state.testKind = "all"; state.testFileLimit = 12; render(); });
item.setAttribute("role", "tab"); item.setAttribute("aria-selected", String(state.tab === name)); nav.append(item);
});
const bar = node("div", "tabs-bar");
const newRun = agentButton("New run", "New run", action => openNewRun(action));
bar.append(nav, newRun); shell.append(bar);
}
function obligationKey(file, line, obligation) { return `${file}:${line}:${obligation.kind || "gap"}:${obligation.id || obligation.missing || obligation.missingCondition || ""}`; }
function allGapKeys(run) {
const values = new Set();
Object.entries(run.fileDetails).forEach(([file, detail]) => detail.gapLines.forEach(line => line.obligations.forEach(obligation => values.add(obligationKey(file, line.line, obligation)))));
return values;
}
function obligationText(obligation) {
if (obligation.missingCondition) return `Prove that “${obligation.missingCondition}” independently changes “${obligation.decision || "this decision"}”.`;
if (obligation.missing) return `Exercise the missing ${obligation.missing} path for ${obligation.source || obligation.kind}.`;
if (obligation.source) return `Execute ${obligation.kind}: ${obligation.source}`;
return `Execute the uncovered ${obligation.kind || "line"} obligation.`;
}
function obligationName(obligation) {
if (obligation.missingCondition) return "Missing MC/DC independence witness";
if (obligation.missing) return `Missing branch: ${obligation.missing}`;
return `Uncovered ${obligation.kind || "line"}`;
}
// Assertion credit is per statement and only exists once a map explains a
// flow, so every reader below returns nothing rather than a zero until then.
function assertionData(run) { return run.assertions && run.assertions.available ? run.assertions : null; }
function assertionFileCount(run, file) { const data = assertionData(run); return data && data.files ? data.files[file] || null : null; }
function assertionStatementsIn(run, file) { const data = assertionData(run); return ((data && data.statements) || []).filter(item => item.file === file); }
// A flow selects tests by file and displayed name, which is how the run's
// test records are named too; matching on both finds the record to open.
function testRecordFor(run, selector) { return selector ? (run.tests || []).find(test => test.role === "test" && test.file === selector.file && test.name === selector.name) || null : null; }
function openTest(test) { state.tab = "tests"; state.testId = test.id; state.testFileFilter = null; state.testKind = "all"; state.testSuite = null; state.testQuery = ""; state.testFileLimit = 12; state.file = null; state.line = null; state.assertLine = null; render(); }
function assertionCreditLabel(run, statements) {
const asserted = statements.find(item => item.asserted);
if (asserted) return `Asserted: ${asserted.test?.name || "a test"} is judged to fail if this statement changes`;
return "Not asserted: no test that runs this statement is judged to fail if it changes";
}
function assertionFileSummary(run, file) {
const counts = assertionFileCount(run, file);
if (!counts || !counts.total) return null;
return `${integer(counts.asserted)} of ${integer(counts.total)} ${counts.total === 1 ? "statement" : "statements"} asserted`;
}
function renderLineEvidence(run, file, line, info, span, stateLabel, claims) {
const panel = node("div", "code-annotation"); panel.setAttribute("role", "region"); panel.setAttribute("aria-label", `Evidence for line ${line}`);
const head = node("div", "line-evidence-head"); head.append(node("strong", "", `Line ${line}`), button("Close", "link", () => { state.assertLine = null; render(); document.querySelector(`.code-row[data-line="${line}"] .code-evidence`)?.focus({ preventScroll: true }); })); panel.append(head);
const coverage = node("section", "line-evidence-section");
const coverageHead = node("div", "line-section-head"); coverageHead.append(node("h4", "", "Coverage"));
if (stateLabel !== "Covered executable line") coverageHead.append(node("span", "line-status", stateLabel));
coverage.append(coverageHead);
const sourceLine = span?.line || line;
const record = info || (span && run.lines.find(value => value.file === file && value.line === span.line));
const ids = new Set(record?.tests || []);
const tests = run.tests.filter(test => test.role === "test" && ids.has(test.id));
if (tests.length) {
coverage.append(node("p", "", `${integer(tests.length)} ${tests.length === 1 ? "test reaches" : "tests reach"} ${span ? `the statement starting on line ${sourceLine}` : "this line"}.`));
const addTest = test => {
const row = button("", "line-test-row", () => openTest(test)); const identity = node("span", "line-test-identity");
identity.append(node("span", "line-test-name", test.name));
if (test.file) identity.append(filePath(test.file, "line-test-file"));
row.append(identity, node("span", "file-chevron", "›")); return row;
};
const list = node("div", "line-tests"); tests.slice(0, 3).forEach(test => list.append(addTest(test))); coverage.append(list);
if (tests.length > 3) {
const more = button(`View all ${integer(tests.length)} tests`, "subtle-button evidence-more", () => openReportModal("evidence", run, more, { title: `Tests reaching line ${line}`, content: searchableEvidence(tests, addTest, test => `${test.name} ${test.file || ""}`, "Find a test…") })); coverage.append(more);
}
} else if (record?.covered || span?.covered) coverage.append(node("p", "", "Execution was recorded without an individual test attribution."));
const gap = run.fileDetails[file]?.gapLines?.find(value => value.line === line);
(gap?.obligations || []).forEach(obligation => {
const entry = node("div", "line-gap"); entry.append(node("strong", "", obligationName(obligation)), node("p", "", obligationText(obligation)));
if (obligation.otherCoverage?.coveredElsewhere) entry.append(node("p", "", `Covered by other selected views: ${obligation.otherCoverage.tests.map(test => test.name).join(", ") || obligation.otherCoverage.kinds.join(", ")}`));
coverage.append(entry);
});
(gap?.limitations || []).forEach(limit => { const entry = node("div", "line-gap"); entry.append(node("strong", "", `Measurement limit: ${limit.kind}`), node("p", "", limit.reason)); coverage.append(entry); });
const decisions = renderDecisions(run, file, line);
if (decisions) {
const more = button("View decision evidence", "subtle-button evidence-more", () => openReportModal("evidence", run, more, { title: `Decision evidence · line ${line}`, content: decisions })); coverage.append(more);
}
panel.append(coverage);
const assertions = node("section", "line-evidence-section"); assertions.append(node("h4", "", "Assertions"));
if (!assertionData(run)) assertions.append(node("p", "", run.assertions?.error || "Assertions were not assessed for this run."));
else if (!claims.length) assertions.append(node("p", "", "Not assessed: no statement a passing test ran starts on this line, or it is an import or declaration."));
else claims.forEach(claim => {
const entry = node("div", "line-gap");
entry.append(node("strong", "", claim.asserted ? "Asserted" : "Not asserted"));
entry.append(node("p", "", claim.asserted
? `${claim.test?.name || "A test"} is judged to fail if this statement is changed (${claim.change}).`
: `No test that runs this statement is judged to fail if it is changed (${claim.change}).`));
const record = testRecordFor(run, claim.test);
if (record) { const actions = node("div", "card-actions"); actions.append(button("Open test \u203a", "subtle-button", () => openTest(record))); entry.append(actions); }
assertions.append(entry);
});
panel.append(assertions); return panel;
}
function renderFileAssertions(run, file) {
const unasserted = assertionStatementsIn(run, file).filter(item => !item.asserted);
if (!assertionData(run) || !unasserted.length) return null;
const card = node("section", "detail-card file-assertions");
const head = node("div", "detail-card-head"); head.append(node("h3", "", "Not asserted"), node("span", "small", `${integer(unasserted.length)} ${unasserted.length === 1 ? "statement" : "statements"}`)); card.append(head);
const makeRow = item => {
const row = button("", "evidence-row", () => { state.line = item.line; state.assertLine = item.line; state.scrollToLine = item.line; state.sourceWindow = null; state.sourceRadius = 32; render(); });
const copy = node("span"); copy.append(node("span", "evidence-name", item.text), node("span", "evidence-meta", `line ${item.line} \u00b7 ${item.change}`));
row.append(copy, node("span", "file-chevron", "\u203a")); return row;
};
const list = node("div", "evidence-list"); unasserted.slice(0, 3).forEach(item => list.append(makeRow(item))); card.append(list);
if (unasserted.length > 3) {
const footer = node("div", "overview-footer");
const more = button(`View all ${integer(unasserted.length)}`, "subtle-button", () => openReportModal("evidence", run, more, { title: "Not asserted", content: searchableEvidence(unasserted, makeRow, item => `${item.text} ${item.line}`, "Find a statement\u2026") }));
footer.append(more); card.append(footer);
}
return card;
}
function renderFileTests(run, file) {
const tests = testsCoveringFile(run, file);
const card = node("section", "detail-card file-tests"); const head = node("div", "detail-card-head");
head.append(node("h3", "", "Tests covering this file"), node("span", "small", `${integer(tests.length)} ${tests.length === 1 ? "test" : "tests"}`)); card.append(head);
if (!tests.length) { card.append(node("p", "quality-context", "No individual tests are linked to this file.")); return card; }
const list = node("div", "evidence-list");
tests.slice(0, 3).forEach(({ test, lines }) => {
const row = button("", "evidence-row", () => openTest(test)); const copy = node("span");
copy.append(node("span", "evidence-name", test.name), node("span", "evidence-meta", `${test.file || test.runner || ""} · ${outcomeLabel(test.outcome)}`));
row.append(copy, node("span", "relation-count", `${integer(lines)} ${lines === 1 ? "line" : "lines"}`), node("span", "file-chevron", "›")); list.append(row);
}); card.append(list);
const footer = node("div", "overview-footer"); footer.append(button(`All ${integer(tests.length)} tests ›`, "link", () => { state.tab = "tests"; state.testFileFilter = file; state.testKind = "all"; state.testSuite = null; state.testId = null; state.testQuery = ""; state.file = null; state.line = null; render(); })); card.append(footer); return card;
}
function renderSource(run, file, selectedLine) {
const wrap = node("div");
const head = node("div", "source-head");
const source = run.sources[file];
const headCopy = node("div", "source-head-copy");
if (!source) headCopy.append(node("div", "small muted", "Only the recorded evidence lines were bundled for this file."));
const legend = node("div", "source-legend");
const assertedStatements = assertionStatementsIn(run, file);
const legendKeys = [["!", "gap", "Open gap"], ["✓", "covered", "Covered executable line"], ["×", "missed", "Missed executable line"], ["·", "", "Non-executable source"]];
if (assertedStatements.length) legendKeys.push(["◆", "asserted", "Asserted"], ["◇", "declared", "Not asserted"]);
legendKeys.forEach(([symbol, tone, label]) => {
const key = node("span", "source-key"); key.append(node("span", `source-key-symbol ${tone}`, symbol), node("span", "", label)); legend.append(key);
});
headCopy.append(legend); head.append(headCopy);
wrap.append(head);
const gapLines = run.fileDetails[file]?.gapLines || [];
if (gapLines.length) {
const navigation = node("div", "gap-lines"); navigation.setAttribute("aria-label", "Coverage gaps");
navigation.append(node("span", "small muted", "Open gaps"));
gapLines.forEach(item => {
const jump = button(`Line ${item.line}`, "line-button", () => { state.line = item.line; state.assertLine = item.line; state.scrollToLine = item.line; state.sourceWindow = null; render(); });
jump.setAttribute("aria-current", String(item.line === state.assertLine)); navigation.append(jump);
});
wrap.append(navigation);
}
const code = node("div", "code"); code.setAttribute("role", "region"); code.setAttribute("aria-label", `Source for ${file}`);
const coverage = new Map(run.lines.filter(item => item.file === file).map(item => [item.line, item]));
const gaps = new Set((run.fileDetails[file]?.gapLines || []).map(item => item.line));
const assertedByLine = new Map(); assertedStatements.forEach(item => { if (!assertedByLine.has(item.line)) assertedByLine.set(item.line, []); assertedByLine.get(item.line).push(item); });
// A statement's coverage is recorded on its first line. A line inside a
// multi-line statement that has no record of its own ran with that first
// line, so it takes the innermost enclosing statement's state; comments
// and blank lines inside still read as non-executable.
const spans = (run.statementSpans || []).filter(span => span.file === file);
const continuationOf = line => { let best = null; spans.forEach(span => { if (line > span.line && line <= span.end && (!best || span.end - span.line < best.end - best.line)) best = span; }); return best; };
const isProse = text => { const trimmed = text.trim(); return !trimmed || /^(\/\/|\/\*|\*)/.test(trimmed); };
let rows = [];
let window = null;
if (source) {
const sourceLines = source.contents.split("\n");
const total = sourceLines.length;
const focus = selectedLine || run.fileDetails[file]?.gapLines?.[0]?.line || assertedStatements.find(item => item.asserted)?.line || 1;
const key = `${file}:${selectedLine || ""}`;
if (!state.sourceWindow || state.sourceWindow.key !== key) state.sourceWindow = { key, start: Math.max(1, focus - state.sourceRadius), end: Math.min(total, focus + state.sourceRadius) };
window = state.sourceWindow;
window.start = Math.max(1, window.start); window.end = Math.min(total, window.end); window.total = total;
rows = sourceLines.slice(window.start - 1, window.end).map((text, offset) => ({ line: window.start + offset, text }));
} else {
rows = (run.fileDetails[file]?.gapLines || []).map(item => ({ line: item.line, text: item.source || "Source text unavailable for this obligation." }));
}
// Expanders in the gutter, the way a diff view unfolds hidden lines:
// a step at a time, or everything at once.
const expander = (direction, hidden) => {
const bar = node("div", "code-expand");
const step = Math.min(hidden, 40);
const stepButton = node("button", "code-expand-button", direction === "up" ? "\u2191" : "\u2193"); stepButton.type = "button"; stepButton.title = `Show ${integer(step)} more ${step === 1 ? "line" : "lines"}`;
stepButton.addEventListener("click", () => { if (direction === "up") window.start -= step; else window.end += step; render(); });
const copy = node("div", "code-expand-copy"); copy.append(node("span", "", `${integer(hidden)} ${hidden === 1 ? "line" : "lines"} ${direction === "up" ? "above" : "below"}`));
const all = node("button", "", "Show all"); all.type = "button"; all.addEventListener("click", () => { window.start = 1; window.end = window.total; render(); }); copy.append(all);
bar.append(stepButton, copy); return bar;
};
if (window && window.start > 1) code.append(expander("up", window.start - 1));
rows.forEach(item => {
const info = coverage.get(item.line); const isGap = gaps.has(item.line);
const span = !info && !isGap && !isProse(item.text) ? continuationOf(item.line) : null;
const stateValue = isGap ? "!" : info ? (info.covered ? "✓" : "×") : span ? (span.covered ? "✓" : "×") : "·";
const stateLabel = isGap ? "Open coverage gap" : info ? (info.covered ? "Covered executable line" : "Missed executable line") : span ? `Continues the ${span.covered ? "covered" : "missed"} statement from line ${span.line}` : "Non-executable source";
const row = node("div", `code-row${item.line === selectedLine ? " selected" : ""}${isGap ? " open-gap" : info ? (info.covered ? " covered" : " uncovered") : span ? (span.covered ? " covered continuation" : " uncovered continuation") : ""}`);
const claims = assertedByLine.get(item.line) || [];
const inherited = !claims.length && span ? (assertedByLine.get(span.line) || []) : [];
const effectiveClaims = claims.length ? claims : inherited;
const credited = effectiveClaims.some(claim => claim.asserted);
const open = state.assertLine === item.line;
const panelId = `line-evidence-${item.line}`;
row.dataset.line = item.line;
const gutter = button("", "code-evidence", () => {
state.assertLine = open ? null : item.line; render();
document.querySelector(`.code-row[data-line="${item.line}"] .code-evidence`)?.focus({ preventScroll: true });
});
const stateMark = node("span", "code-state", stateValue);
const assertMark = node("span", `code-assert${credited ? "" : effectiveClaims.length ? " declared" : " absent"}`, credited ? "◆" : effectiveClaims.length ? "◇" : "·");
stateMark.setAttribute("aria-hidden", "true"); assertMark.setAttribute("aria-hidden", "true");
const assertionLabel = effectiveClaims.length ? assertionCreditLabel(run, effectiveClaims) : "Assertions not assessed on this line";
gutter.title = `${stateLabel} · ${assertionLabel}`;
gutter.setAttribute("aria-label", `Line ${item.line}: ${stateLabel}. ${assertionLabel}. Show coverage and assertions`);
gutter.setAttribute("aria-expanded", String(open)); gutter.setAttribute("aria-controls", panelId);
gutter.append(stateMark, assertMark);
if (open) row.classList.add("evidence-open");
row.append(node("span", "code-no", item.line), gutter, node("span", "code-text", item.text)); code.append(row);
if (open) {
const annotation = renderLineEvidence(run, file, item.line, info, span, stateLabel, effectiveClaims);
annotation.id = panelId; code.append(annotation);
}
});
if (window && window.end < window.total) code.append(expander("down", window.total - window.end));
wrap.append(code);
if (state.scrollToLine) { const target = state.scrollToLine; state.scrollToLine = null; requestAnimationFrame(() => { const row = [...document.querySelectorAll(".code-row")].find(candidate => candidate.querySelector(".code-no")?.textContent === String(target)); if (row) row.scrollIntoView({ block: "center" }); }); }
return wrap;
}
function renderDecisions(run, file, selectedLine) {
const decisions = run.decisions.filter(decision => decision.meta.file === file && (!selectedLine || decision.meta.line === selectedLine));
if (!decisions.length) return null;
const section = node("div", "decision-evidence");
decisions.forEach(decision => {
const card = node("div", "decision"); card.append(node("div", "decision-head", `${decision.meta.line}:${decision.meta.column} · ${decision.meta.source}`));
const body = node("div", "decision-body");
decision.conditions.forEach(condition => {
const row = node("div", "condition"); row.append(node("span", `status-dot ${condition.covered ? "good" : "bad"}`));
const copy = node("div"); copy.append(node("div", "", condition.source), node("div", "small muted", condition.covered ? "Independence witness found" : "Missing an independence witness")); row.append(copy); body.append(row);
});
if (decision.vectors.length) {
const vectors = node("div", "vectors"); decision.vectors.slice(0, 24).forEach(vector => vectors.append(node("span", "vector", `${vector.values.map(value => value === null ? "–" : value ? "T" : "F").join(" ")} → ${vector.outcome ? "T" : "F"}`))); body.append(vectors);
}
card.append(body); section.append(card);
});
return section;
}
function testsCoveringFile(run, file) {
return (run.tests || []).filter(test => test.role === "test").map(test => {
const lines = new Set((test.lines || []).filter(line => line.file === file).map(line => line.line));
return { test, lines: lines.size };
}).filter(value => value.lines > 0).sort((a, b) => b.lines - a.lines || a.test.name.localeCompare(b.test.name));
}
function testTone(test) {
const outcome = String(test.outcome || "").toLowerCase();
if (/fail|error|timed.?out|interrupt/.test(outcome)) return "bad";
if (/flaky|skip|ignored|todo|pending/.test(outcome)) return "warn";
if (/pass|success|ok/.test(outcome)) return "good";
return "neutral";
}
function outcomeLabel(value) {
const text = String(value || "Unknown").replace(/[_-]+/g, " ");
return text.charAt(0).toUpperCase() + text.slice(1);
}
function kindLabel(value) {
const known = { e2e: "End-to-end", unit: "Unit", integration: "Integration", doctest: "Documentation" };
return known[value] || outcomeLabel(value);
}
function filesCoveredByTest(test) {
const files = new Map();
(test.lines || []).forEach(line => {
if (!files.has(line.file)) files.set(line.file, new Set());
files.get(line.file).add(line.line);
});
return [...files.entries()].map(([file, lines]) => ({ file, lines: lines.size })).sort((a, b) => b.lines - a.lines || a.file.localeCompare(b.file));
}
function testSuiteKey(test) {
return test.file || [test.runner, test.kind].filter(Boolean).join(":") || "Recorded tests";
}
function testTitle(test) {
return test.title || test.name || "Unnamed test";
}
function testParent(test) {
const name = String(test.name || ""), title = String(test.title || "");
if (!title || name === title || !name.endsWith(title)) return "";
return name.slice(0, -title.length).replace(/[\s>:/:]+$/g, "");
}
function testGroupName(test) {
return testParent(test).split(/\s+>\s+/)[0] || "Tests";
}
function testGroupContext(test) {
return testParent(test).split(/\s+>\s+/).slice(1).join(" › ");
}
function testTonePriority(value) {
return ({ bad: 0, warn: 1, neutral: 2, good: 3 })[value] ?? 2;
}
function groupedTestFiles(tests) {
const groups = new Map();
tests.forEach(test => {
const key = testSuiteKey(test);
if (!groups.has(key)) groups.set(key, { key, tests: [] });
groups.get(key).tests.push(test);
});
return [...groups.values()].map(group => ({
...group,
tone: group.tests.map(test => testTone(test)).sort((a, b) => testTonePriority(a) - testTonePriority(b))[0] || "neutral"
})).sort((a, b) => testTonePriority(a.tone) - testTonePriority(b.tone) || a.key.localeCompare(b.key));
}
function groupedTestsInFile(tests) {
const groups = new Map();
tests.forEach(test => {
const label = testGroupName(test);
if (!groups.has(label)) groups.set(label, []);
groups.get(label).push(test);
});
return [...groups.entries()].map(([label, values]) => ({
label,
tests: values.sort((a, b) => testTonePriority(testTone(a)) - testTonePriority(testTone(b)) || testTitle(a).localeCompare(testTitle(b))),
tone: values.map(test => testTone(test)).sort((a, b) => testTonePriority(a) - testTonePriority(b))[0] || "neutral"
})).sort((a, b) => testTonePriority(a.tone) - testTonePriority(b.tone) || a.label.localeCompare(b.label));
}
function testHealthParts(tests) {
const counts = { bad: 0, warn: 0, neutral: 0, good: 0 };
tests.forEach(test => counts[testTone(test)]++);
const parts = [];
if (counts.bad) parts.push(`${integer(counts.bad)} failed`);
if (counts.warn) parts.push(`${integer(counts.warn)} need review`);
if (counts.neutral) parts.push(`${integer(counts.neutral)} without a result`);
if (counts.good) parts.push(`${integer(counts.good)} passed`);
return parts;
}
function testHealthSummary(tests, fileCount) {
const health = testHealthParts(tests).join(" · ") || "No tests";
if (!fileCount) return health;
return `${health} across ${integer(fileCount)} test ${fileCount === 1 ? "file" : "files"}`;
}
function filesCoveredByTests(tests) {
return new Set(tests.flatMap(test => (test.lines || []).map(line => line.file))).size;
}
function meaningfulAttemptLabel(test) {
const attempts = Array.isArray(test.attempts) ? test.attempts : [];
const retried = attempts.some(attempt => Number(attempt.retry) > 0) || (test.retries || []).some(retry => Number(retry) > 0);
if (attempts.length > 1) return `${integer(attempts.length)} attempts`;
return retried ? "Retried" : "";
}
function renderFileStats(run, file, quality) {
const stats = node("div", "detail-stats");
const stat = (label, value, tone = "", mark = null, improve = false) => {
const cell = node("div", "detail-stat"); cell.append(node("div", "detail-stat-label", label));
const line = node("div", "detail-stat-line"); if (mark) line.append(mark);
const valueNode = node("div", `detail-stat-value${tone ? ` ${tone}` : ""}`);
if (typeof value === "string") valueNode.textContent = value; else valueNode.append(value);
line.append(valueNode); cell.append(line);
const actions = node("div", "detail-stat-actions");
if (label === "Coverage") {
const breakdown = button("View breakdown", "subtle-button", () => openReportModal("coverage", run, breakdown, null, file));
breakdown.setAttribute("aria-haspopup", "dialog"); actions.append(breakdown);
}
if (improve) actions.append(improveButton(label.toLowerCase(), file));
if (actions.childElementCount) cell.append(actions); stats.append(cell);
};
const record = run?.files.find(value => value.file === file);
// The same pooled count as the overview and this file's breakdown: one
// file's coverage read 80% here (its lines) and 70.7% on the overview.
const filePool = run ? fileObligationPool(run, file) : null;
const pooled = (() => {
if (!filePool) return null;
let total = 0, open = 0;
Object.values(filePool.kinds).forEach(kind => { total += kind.total; open += Math.min(kind.open, kind.total); });
return total ? (total - open) / total * 100 : null;
})();
const openCount = record ? missing(record) + record.measurementLimitations : 0;
stat("Coverage", record?.measurementLimitations ? "Limited" : pooled === null ? "—" : pct(pooled), openCount ? "open" : "", markCoverage(pooled, 26, openCount), pooled === null || openCount > 0);
const counts = run && assertionFileCount(run, file);
const share = counts?.total ? counts.asserted / counts.total * 100 : null;
stat("Assertions", share === null ? "—" : pct(share), share === null ? "quiet" : "", markCredit(share, 26), share === null || share < 100);
const assessed = qualityRow(quality, file);
if (assessed) {
const value = document.createDocumentFragment(); value.append(document.createTextNode(health(assessed.health)), node("span", `pill ${band(assessed.health)}`, band(assessed.health)));
stat("Quality", value, "", markQuality(assessed.health, 26), Number(assessed.health) < 10);
} else stat("Quality", "—", "quiet", markQuality(null, 26), true);
stat("Security", securityLabel(file), securityCount(file) !== null ? "" : "quiet", markSecurity(26, securityLabel(file)), true);
return stats;
}
function renderFileDetail(run, file) {
const panel = node("section", "panel detail detail-view");
const record = run.files.find(value => value.file === file);
if (!record) { panel.append(node("div", "empty", "Choose a file to inspect it.")); return panel; }
const top = node("div", "detail-top"); top.append(button("‹ All files", "back-button", () => { state.file = null; state.line = null; render(); }), filePath(file, "detail-heading", "h1"));
const stats = renderFileStats(run, file, currentQuality());
top.append(stats);
panel.append(top);
const sourceSection = node("div", "section"); sourceSection.append(node("h3", "", "Source"), renderSource(run, file, state.line)); panel.append(sourceSection);
panel.append(renderFileTests(run, file));
const assertions = renderFileAssertions(run, file); if (assertions) panel.append(assertions);
const fileQuality = renderFileQuality(file); if (fileQuality) panel.append(fileQuality);
panel.append(renderSecurity(true));
return panel;
}
function renderPlainSource(quality, file) {
const wrap = node("div");
const source = quality.sources && quality.sources[file];
const head = node("div", "source-head"); const headCopy = node("div", "source-head-copy");
if (!source) {
headCopy.append(node("div", "small muted", (quality.omittedSources || []).includes(file)
? "This file changed after it was assessed, so its source is not shown beside a grade that describes different text."
: "Source for this file was not bundled."));
head.append(headCopy); wrap.append(head); return wrap;
}
headCopy.append(node("div", "small muted", "The source as it was assessed."));
head.append(headCopy); wrap.append(head);
const code = node("div", "code"); code.setAttribute("role", "region"); code.setAttribute("aria-label", `Source for ${file}`);
const lines = source.contents.split("\n");
const end = Math.min(lines.length, state.sourceRadius * 2);
lines.slice(0, end).forEach((text, offset) => {
const row = node("div", "code-line");
row.append(node("span", "code-gutter", String(offset + 1)), node("span", "code-flag", "\u00b7"), node("span", "code-text", text || " "));
code.append(row);
});
if (end < lines.length) {
const hidden = lines.length - end; const step = Math.min(hidden, 40);
const bar = node("div", "code-expand");
const stepButton = node("button", "code-expand-button", "\u2193"); stepButton.type = "button"; stepButton.title = `Show ${integer(step)} more ${step === 1 ? "line" : "lines"}`;
stepButton.addEventListener("click", () => { state.sourceRadius += Math.ceil(step / 2); render(); });
const copy = node("div", "code-expand-copy"); copy.append(node("span", "", `${integer(hidden)} ${hidden === 1 ? "line" : "lines"} below`));
const all = node("button", "", "Show all"); all.type = "button"; all.addEventListener("click", () => { state.sourceRadius = lines.length; render(); }); copy.append(all);
bar.append(stepButton, copy); code.append(bar);
}
wrap.append(code); return wrap;
}
function renderQualityFileDetail(content, quality, file) {
const panel = node("section", "panel detail detail-view");
const top = node("div", "detail-top");
top.append(button("\u2039 All files", "back-button", () => { state.file = null; state.line = null; render(); }), filePath(file, "detail-heading", "h1"));
top.append(renderFileStats(null, file, quality)); panel.append(top);
const sourceSection = node("div", "section");
sourceSection.append(node("h3", "", "Source"), renderPlainSource(quality, file));
panel.append(sourceSection);
const fileQuality = renderFileQuality(file); if (fileQuality) panel.append(fileQuality);
panel.append(renderMissing("run", "Not measured", "No coverage run read this source, so nothing here says which of these lines the tests reach."), renderSecurity(true));
content.append(panel);
}
function renderQualityFiles(content, quality) {
if (!["name", "quality", "security"].includes(state.fileSort)) { state.fileSort = "quality"; state.fileSortDescending = false; }
if (state.file && Array.isArray(quality.files) && quality.files.some(file => file.path === state.file)) {
renderQualityFileDetail(content, quality, state.file); return;
}
if (state.file && securityFilePaths().includes(state.file)) { renderSecurityFiles(content); return; }
state.file = null;
const entries = new Map((quality.files || []).map(file => [file.path, file]));
for (const path of securityFilePaths()) if (!entries.has(path)) entries.set(path, { path, health: null });
const missingLast = value => value == null ? (state.fileSortDescending ? -Infinity : Infinity) : value;
const files = [...entries.values()]
.filter(file => file.path.toLowerCase().includes(state.gapQuery.toLowerCase()))
.slice().sort((a, b) => (state.fileSort === "name" ? a.path.localeCompare(b.path) : state.fileSort === "security" ? missingLast(securityCount(a.path)) - missingLast(securityCount(b.path)) : missingLast(a.health) - missingLast(b.health)) * (state.fileSortDescending ? -1 : 1) || a.path.localeCompare(b.path));
const panel = node("section", "panel file-list-panel collection-view");
const head = node("div", "file-list-head"); const title = node("div");
title.append(node("div", "panel-title", "Files"), node("div", "small muted", `${integer(files.length)} assessed${currentRun() ? "" : " · no coverage run for this source"}`));
head.append(title);
const search = node("input", "file-search"); search.type = "search"; search.placeholder = "Find a file…"; search.value = state.gapQuery;
search.addEventListener("input", () => { const cursor = search.selectionStart; state.gapQuery = search.value; render(); const next = document.querySelector(".file-search"); next?.focus(); next?.setSelectionRange(cursor, cursor); });
const tools = node("div", "file-list-tools"); tools.append(search); head.append(tools); panel.append(head);
const list = node("div", "file-list");
files.forEach(file => {
const row = button("", "file-row", () => { state.file = file.path; state.line = null; state.sourceRadius = 32; render(); });
row.append(node("span", `file-state-dot ${band(file.health)}`));
const identity = node("span", "file-row-identity");
identity.append(filePath(file.path));
row.append(identity);
const trail = node("span", "file-row-marks");
const run = currentRun(), lines = (run?.lines || []).filter(line => line.file === file.path);
const share = lines.length ? lines.filter(line => line.covered).length / lines.length * 100 : null;
const record = run?.files.find(entry => entry.file === file.path);
const counts = run && assertionFileCount(run, file.path), asserted = counts?.total ? counts.asserted / counts.total * 100 : null;
[[markCoverage(share, 22, record ? missing(record) : 0), share === null ? "—" : pct(share)], [markCredit(asserted, 22), asserted === null ? "—" : pct(asserted)], [markQuality(file.health, 22), file.health == null ? "—" : health(file.health)]].forEach(([chart, value]) => { const cell = node("span", "file-row-mark"); cell.append(chart, node("span", "", value)); trail.append(cell); });
trail.append(securityFileMark(file.path)); labelFileMetrics(trail); row.append(trail);
const chevron = node("span", "file-chevron", "\u203a"); chevron.setAttribute("aria-hidden", "true"); row.append(chevron);
list.append(row);
});
if (!files.length) list.append(node("div", "empty", "No files match this search."));
const table = node("div", "file-table"); const columns = node("div", "file-head");
const sort = key => { state.fileSortDescending = state.fileSort === key ? !state.fileSortDescending : key === "security"; state.fileSort = key; render(); };
columns.append(button(`File${state.fileSort === "name" ? state.fileSortDescending ? " ↓" : " ↑" : ""}`, "file-head-cell", () => sort("name")));
const marks = node("span", "file-head-marks"); marks.append(node("span", "file-head-cell", "Coverage"), node("span", "file-head-cell", "Assertions"), button(`Quality${state.fileSort === "quality" ? state.fileSortDescending ? " ↓" : " ↑" : ""}`, "file-head-cell", () => sort("quality")));
const securityHead = button(`Security${state.fileSort === "security" ? state.fileSortDescending ? " ↓" : " ↑" : ""}`, "file-head-cell", () => sort("security"));
securityHead.setAttribute("aria-sort", state.fileSort === "security" ? state.fileSortDescending ? "descending" : "ascending" : "none");
marks.append(securityHead);
columns.append(marks, node("span")); table.append(columns, list); panel.append(table); appendCrossFileFindings(panel); content.append(panel);
}
// What the assessment says about one file, beside the source rather than on
// it. Every property is a claim about the whole file, and the command line
// is careful never to guess a line for one, so neither is this.
function renderFileQuality(file) {
const quality = currentQuality();
const section = node("section", "detail-card file-quality");
const heading = node("div", "detail-card-head"); heading.append(node("h3", "", "Quality")); section.append(heading);
const row = qualityRow(quality, file);
if (!row) { const summary = node("div", "file-quality-summary"); summary.append(markQuality(null, 48), node("span", "quality-score quiet", "—"), node("span", "quality-context", "Not assessed")); section.append(summary); return section; }
const summary = node("div", "file-quality-summary"); summary.append(markQuality(row.health, 48));
const score = node("div", "quality-score-line"); score.append(node("div", "quality-score", health(row.health)), node("span", `pill quality-rating ${band(row.health)}`, band(row.health))); summary.append(score); section.append(summary);
const fired = row.present || [];
fired.forEach(check => {
const definition = (Array.isArray(quality.catalog) ? quality.catalog : []).find(entry => entry.check === check.check);
const concern = node("div", "quality-concern");
const label = check.check.replace(/_/g, " ");
const heading = node("div", "quality-concern-heading"); heading.append(node("strong", "", label.charAt(0).toUpperCase() + label.slice(1)), concernValue(check.value, "quality-concern-value")); concern.append(heading);
if (definition?.asks) concern.append(node("p", "", definition.asks)); section.append(concern);
});
if (!fired.length) section.append(node("p", "quality-context", "No concerns recorded."));
if (row.warning) section.append(node("p", "evidence-meta", row.warning));
return section;
}
function renderExplore(content, run) {
if (state.file && run.files.some(file => file.file === state.file)) { content.append(renderFileDetail(run, state.file)); return; }
const selectedQuality = currentQuality();
if (state.qualityFiles && selectedQuality) { renderQualityFiles(content, selectedQuality); return; }
if (state.file && !run.files.some(file => file.file === state.file) && qualityRow(selectedQuality, state.file)) { renderQualityFileDetail(content, selectedQuality, state.file); return; }
if (state.file && securityFilePaths().includes(state.file)) { renderSecurityFiles(content); return; }
state.file = null;
const lineTotals = new Map();
run.lines.forEach(line => { const entry = lineTotals.get(line.file) || { covered: 0, total: 0 }; entry.total += 1; if (line.covered) entry.covered += 1; lineTotals.set(line.file, entry); });
const lineCoverage = file => { const entry = lineTotals.get(file); return entry && entry.total ? entry.covered / entry.total * 100 : null; };
const quality = currentQuality();
// Every column sorts, and the header says which one and in which direction:
// a list whose order cannot be read is a list that looks arbitrary.
const owedOf = file => missing(file) + (file.measurementLimitations || 0);
const creditOf = file => { const counts = assertionFileCount(run, file.file); return counts && counts.total ? counts.asserted / counts.total * 100 : null; };
const healthOf = file => { const row = qualityRow(quality, file.file); return row && Number.isFinite(Number(row.health)) ? Number(row.health) : null; };
// A file a judgment does not cover sorts last in either direction rather than
// pretending to be a zero or a hundred.
const missingLast = (value, descending) => value === null || value === undefined ? (descending ? -Infinity : Infinity) : value;
const columns = {
name: { label: "File", compare: (a, b) => a.file.localeCompare(b.file), descending: false },
// Ascending coverage puts the files still owing work at the top, which is
// the order a reader wants before they have chosen one of their own.
coverage: { label: "Coverage", compare: (a, b, d) => (missingLast(lineCoverage(a.file), d) - missingLast(lineCoverage(b.file), d)) || (owedOf(b) - owedOf(a)), descending: false },
asserted: { label: "Assertions", compare: (a, b, d) => missingLast(creditOf(a), d) - missingLast(creditOf(b), d), descending: false },
quality: { label: "Quality", compare: (a, b, d) => missingLast(healthOf(a), d) - missingLast(healthOf(b), d), descending: false },
security: { label: "Security", compare: (a, b, d) => missingLast(securityCount(a.file), d) - missingLast(securityCount(b.file), d), descending: true }
};
if (!columns[state.fileSort]) state.fileSort = "coverage";
if (typeof state.fileSortDescending !== "boolean") state.fileSortDescending = columns[state.fileSort].descending;
const activeColumn = columns[state.fileSort];
const sortFiles = (a, b) => {
const order = activeColumn.compare(a, b, state.fileSortDescending) || 0;
return (state.fileSortDescending ? -order : order) || a.file.localeCompare(b.file);
};
// A kind picked on the overview narrows this list. The scope is spelled out
// and dismissible above the list, never a filter the reader cannot see.
let scopeNote = null;
const scopeKind = OBLIGATION_KINDS.find(kind => kind.id === state.fileKind) || null;
if (state.fileKind && !scopeKind) state.fileKind = null;
const allFiles = new Map(run.files.map(file => [file.file, file]));
for (const file of securityFilePaths()) if (!allFiles.has(file)) allFiles.set(file, { file });
const scoped = scopeKind ? run.files.filter(file => (Number(file[scopeKind.field]) || 0) > 0) : [...allFiles.values()];
const values = scoped.filter(file => file.file.toLowerCase().includes(state.gapQuery.toLowerCase())).sort(sortFiles);
if (scopeKind) {
const owed = scoped.reduce((sum, file) => sum + (Number(file[scopeKind.field]) || 0), 0);
const note = node("div", "scope-note");
note.append(markState(owed, 14));
note.append(node("b", "", scopeKind.label));
// The noun agrees with what is owed, not with how many files owe it.
note.append(node("span", "", `${scoped.length === 1 ? "one file" : `${integer(scoped.length)} files`} still ${scoped.length === 1 ? "owes" : "owe"} ${integer(owed)} ${owed === 1 ? scopeKind.one : scopeKind.many}`));
note.append(button("Show every file \u2715", "link", () => { state.fileKind = null; render(); }));
scopeNote = note;
}
const panel = node("section", "panel file-list-panel collection-view");
const attention = gapFiles(run).length; const head = node("div", "file-list-head"); const title = node("div"); title.append(node("div", "panel-title", "Files"), node("div", "small muted", attention ? `${integer(attention)} of ${integer(run.files.length)} measured files need attention` : `${integer(run.files.length)} measured files, all covered`));
if (allFiles.size > run.files.length) title.lastChild.textContent = `${integer(allFiles.size)} files · ${integer(run.files.length)} measured by tests`;
const tools = node("div", "file-list-tools");
const search = node("input", "file-search"); search.type = "search"; search.placeholder = "Find a file\u2026"; search.value = state.gapQuery; search.addEventListener("input", () => { const cursor = search.selectionStart; state.gapQuery = search.value; render(); const next = document.querySelector(".file-search"); if (next) { next.focus(); next.setSelectionRange(cursor, cursor); } });
tools.append(search); head.append(title, tools); panel.append(head);
if (scopeNote) panel.append(scopeNote);
const columnRow = node("div", "file-head");
const sortBy = key => {
state.fileSortDescending = state.fileSort === key ? !state.fileSortDescending : columns[key].descending;
state.fileSort = key;
render();
};
const headCell = (key, className) => {
const active = state.fileSort === key;
const cell = button("", `file-head-cell ${className}${active ? " active" : ""}`, () => sortBy(key));
cell.append(node("span", "", columns[key].label));
cell.append(node("span", "file-head-arrow", active ? (state.fileSortDescending ? "\u2193" : "\u2191") : ""));
cell.setAttribute("aria-sort", active ? (state.fileSortDescending ? "descending" : "ascending") : "none");
cell.title = `Sort by ${columns[key].label.toLowerCase()}`;
return cell;
};
columnRow.append(headCell("name", "file-head-name"));
const columnMarks = node("span", "file-head-marks");
columnMarks.append(headCell("coverage", ""));
columnMarks.append(headCell("asserted", ""));
columnMarks.append(headCell("quality", ""));
columnMarks.append(headCell("security", ""));
columnRow.append(columnMarks, node("span", "file-head-end"));
const table = node("div", "file-table"); table.append(columnRow);
const list = node("div", "file-list");
values.forEach(file => {
const open = missing(file) > 0, limited = file.measurementLimitations > 0;
const row = button("", "file-row", () => { state.file = file.file; state.line = null; state.assertLine = null; state.sourceRadius = 32; render(); });
const dotLabel = limited ? "Measurement limit" : open ? "Obligations left to reach" : "Every obligation reached"; const dot = node("span", `file-state-dot${limited ? " limited" : open ? " open" : ""}`); dot.title = dotLabel; dot.setAttribute("aria-label", dotLabel); row.append(dot);
const identity = node("span", "file-row-identity"); identity.append(filePath(file.file));
const owedHere = scopeKind ? Number(file[scopeKind.field]) || 0 : 0;
const context = owedHere ? `${integer(owedHere)} ${owedHere === 1 ? scopeKind.one : scopeKind.many} left to reach` : "";
if (context) identity.append(node("span", `file-row-directory${owedHere ? " open" : ""}`, context));
row.append(identity);
const owed = missing(file);
const trail = node("span", "file-row-marks");
// The judgments in one fixed order, each number beside its own mark,
// so a row reads before the shapes have been learned.
const share = lineCoverage(file.file);
const coverage = node("span", `file-row-mark${owed ? " open" : ""}`);
coverage.append(markCoverage(share, 22, owed), node("span", "", owed ? integer(owed) : share === null ? "\u2014" : pct(share)));
coverage.title = owed ? `${integer(owed)} ${owed === 1 ? "obligation" : "obligations"} left to reach: ${fileGapSummary(file)}` : share === null ? "Not measured" : "Every obligation reached";
trail.append(coverage);
const counts = assertionFileCount(run, file.file);
if (counts && counts.total) {
const share2 = counts.asserted / counts.total * 100;
const credit = node("span", "file-row-mark");
credit.append(markCredit(share2, 22), node("span", "", pct(share2)));
credit.title = assertionFileSummary(run, file.file); trail.append(credit);
} else { const credit = node("span", "file-row-mark quiet"); credit.append(markCredit(null, 22), node("span", "", "\u2014")); credit.title = "No measured statement in this file carries credit yet"; trail.append(credit); }
const assessed = qualityRow(quality, file.file);
if (assessed) { const mark = node("span", "file-row-mark"); mark.append(markQuality(assessed.health, 22), node("span", "", health(assessed.health))); mark.title = `Quality ${health(assessed.health)}, the ${band(assessed.health)} band`; trail.append(mark); }
else { const mark = node("span", "file-row-mark quiet"); mark.append(markQuality(null, 22), node("span", "", "\u2014")); mark.title = "Not assessed"; trail.append(mark); }
trail.append(securityFileMark(file.file));
if (limited) trail.append(node("span", "file-row-metric limited", "Limited"));
labelFileMetrics(trail); row.append(trail);
const chevron = node("span", "file-chevron", "\u203a"); chevron.setAttribute("aria-hidden", "true"); row.append(chevron); list.append(row);
});
if (!values.length) list.append(node("div", "empty", "No files match this search.")); table.append(list); panel.append(table); appendCrossFileFindings(panel); content.append(panel);
}
function renderTests(content, run) {
const tests = (run.tests || []).filter(test => test.role === "test");
const scoped = state.testFileFilter ? tests.filter(test => (test.lines || []).some(line => line.file === state.testFileFilter)) : tests;
const kinds = [...new Set(scoped.map(test => test.kind).filter(Boolean))].sort((a, b) => kindLabel(a).localeCompare(kindLabel(b)));
if (state.testKind !== "all" && !kinds.includes(state.testKind)) state.testKind = "all";
const byKind = state.testKind === "all" ? scoped : scoped.filter(test => test.kind === state.testKind);
const testFiles = groupedTestFiles(byKind);
const query = state.testQuery.trim().toLowerCase();
const matchesQuery = test => !query || `${test.name} ${test.title || ""} ${test.file || ""} ${test.runner || ""} ${test.kind || ""}`.toLowerCase().includes(query);
if (state.testId) {
const selected = byKind.find(test => test.id === state.testId);
if (selected) {
const detail = node("section", "panel detail detail-view test-detail"); const detailHead = node("div", "detail-card test-detail-header");
const backLabel = selected.file ? `‹ ${selected.file}` : "‹ Test file";
detail.append(button(backLabel, "back-button", () => { state.testId = null; state.testSuite = testSuiteKey(selected); state.testFileLimit = 12; render(); })); detailHead.append(node("h1", "detail-heading", testTitle(selected)));
const parent = testParent(selected); const context = [parent, selected.file || ""].filter(Boolean).join(" · "); if (context) detailHead.append(node("div", "detail-subtitle", context));
const files = filesCoveredByTest(selected), attempt = meaningfulAttemptLabel(selected), tone = testTone(selected); const summaryParts = [outcomeLabel(selected.outcome), kindLabel(selected.kind), `${integer(files.length)} covered ${files.length === 1 ? "file" : "files"}`, attempt].filter(Boolean); const summary = node("div", "detail-summary"); summary.append(node("span", `test-state-dot ${tone}`), node("span", "", summaryParts.join(" · "))); detailHead.append(summary); detail.append(detailHead);
const filesSection = node("div", "detail-card test-covered-files"); const filesHead = node("div", "detail-card-head"); filesHead.append(node("h3", "", "Covered source files")); filesSection.append(filesHead);
if (!files.length) filesSection.append(node("p", "relation-empty", "No source coverage was attributed to this test."));
else {
const fileList = node("div", "relation-list"); files.slice(0, state.testFileLimit).forEach(({ file, lines }) => {
const row = button("", "relation-row", () => { state.tab = "explore"; state.file = file; state.line = null; state.sourceRadius = 32; state.testSuite = null; state.testId = null; state.testFileFilter = null; render(); });
const identity = node("span", "relation-identity"); identity.append(filePath(file, "relation-name")); const chevron = node("span", "file-chevron", "›"); chevron.setAttribute("aria-hidden", "true"); row.append(identity, node("span", "relation-count", `${integer(lines)} ${lines === 1 ? "line" : "lines"}`), chevron); fileList.append(row);
});
filesSection.append(fileList); if (files.length > state.testFileLimit) filesSection.append(button(`Show all ${integer(files.length)} files`, "relation-more", () => { state.testFileLimit = files.length; render(); }));
}
detail.append(filesSection); content.append(detail); return;
}
state.testId = null;
}
if (state.testSuite) {
const selected = testFiles.find(testFile => testFile.key === state.testSuite);
if (selected) {
const values = selected.tests.filter(matchesQuery).sort((a, b) => testTonePriority(testTone(a)) - testTonePriority(testTone(b)) || testTitle(a).localeCompare(testTitle(b)));
const panel = node("section", "panel file-list-panel collection-view"); panel.append(button("‹ All test files", "back-button", () => { state.testSuite = null; state.testId = null; state.testQuery = ""; render(); }));
const head = node("div", "file-list-head"); const title = node("div"); title.append(filePath(selected.key, "panel-title", "div"));
const coveredFiles = filesCoveredByTests(selected.tests); const kindNames = [...new Set(selected.tests.map(test => test.kind).filter(Boolean))].map(kindLabel); const health = testHealthParts(selected.tests).join(" · "); title.append(node("div", "small muted", [health, kindNames.join(" + "), `${integer(coveredFiles)} covered source ${coveredFiles === 1 ? "file" : "files"}`].filter(Boolean).join(" · "))); head.append(title);
const search = node("input", "test-search"); search.type = "search"; search.placeholder = "Find a test in this file…"; search.value = state.testQuery; search.addEventListener("input", () => { const cursor = search.selectionStart; state.testQuery = search.value; render(); const next = document.querySelector(".test-search"); if (next) { next.focus(); next.setSelectionRange(cursor, cursor); } }); head.append(search); panel.append(head);
const list = node("div", "file-list test-list test-table");
groupedTestsInFile(values).forEach(group => {
const section = node("section", "test-group"); const groupHead = node("div", "test-group-head"); groupHead.append(node("span", "test-group-name", group.label), node("span", "test-group-count", `${integer(group.tests.length)} ${group.tests.length === 1 ? "test" : "tests"}`)); section.append(groupHead);
group.tests.forEach(test => {
const tone = testTone(test); const files = filesCoveredByTest(test); const row = button("", "test-row", () => { state.testId = test.id; state.testFileLimit = 12; render(); });
const dot = node("span", `test-state-dot ${tone}`); const dotLabel = `${outcomeLabel(test.outcome)} test`; dot.title = dotLabel; dot.setAttribute("aria-label", dotLabel); row.append(dot);
const identity = node("span", "test-row-identity"); identity.append(node("span", "test-row-name", testTitle(test))); const context = testGroupContext(test); if (context) identity.append(node("span", "test-row-context", context)); row.append(identity, node("span", "test-row-metric", files.length ? `${integer(files.length)} ${files.length === 1 ? "file" : "files"}` : ""));
const chevron = node("span", "file-chevron", "›"); chevron.setAttribute("aria-hidden", "true"); row.append(chevron); section.append(row);
});
list.append(section);
});
if (!values.length) list.append(node("div", "empty", "No tests match this search.")); panel.append(list); content.append(panel); return;
}
state.testSuite = null;
}
const visibleFiles = testFiles.map(testFile => ({ ...testFile, matches: testFile.tests.filter(matchesQuery) })).filter(testFile => testFile.matches.length);
const visibleTests = visibleFiles.flatMap(testFile => testFile.matches);
const listPanel = node("section", "panel file-list-panel collection-view");
if (state.testFileFilter) listPanel.append(button("‹ All tests", "back-button", () => { state.testFileFilter = null; state.testKind = "all"; state.testQuery = ""; render(); }));
const head = node("div", "file-list-head"); const title = node("div");
let summary = query ? `${integer(visibleTests.length)} ${visibleTests.length === 1 ? "match" : "matches"} across ${integer(visibleFiles.length)} test ${visibleFiles.length === 1 ? "file" : "files"}` : testHealthSummary(byKind, testFiles.length);
if (state.testFileFilter) summary += ` · covering ${state.testFileFilter}`;
title.append(node("div", "panel-title", "Tests"), node("div", "small muted", summary)); head.append(title);
if (kinds.length > 1) {
const filters = node("div", "test-filters"); [["all", "All"], ...kinds.map(kind => [kind, kindLabel(kind)])].forEach(([kind, label]) => { const item = button(label, "test-filter", () => { state.testKind = kind; state.testSuite = null; state.testId = null; state.testFileLimit = 12; render(); }); item.setAttribute("aria-pressed", String(state.testKind === kind)); filters.append(item); }); head.append(filters);
}
const search = node("input", "test-search"); search.type = "search"; search.placeholder = "Find a test file or test…"; search.value = state.testQuery; search.addEventListener("input", () => { const cursor = search.selectionStart; state.testQuery = search.value; render(); const next = document.querySelector(".test-search"); if (next) { next.focus(); next.setSelectionRange(cursor, cursor); } }); head.append(search); listPanel.append(head);
const list = node("div", "file-list test-list test-table");
visibleFiles.forEach(testFile => {
const row = button("", "file-row", () => { state.testSuite = testFile.key; state.testId = null; state.testFileLimit = 12; render(); });
const dot = node("span", `test-state-dot ${testFile.tone}`); const dotLabel = `${testHealthParts(testFile.tests).join(", ")} in this test file`; dot.title = dotLabel; dot.setAttribute("aria-label", dotLabel); row.append(dot);
const identity = node("span", "file-row-identity"); identity.append(filePath(testFile.key)); row.append(identity);
const count = query ? testFile.matches.length : testFile.tests.length; row.append(node("span", "file-row-metric", `${integer(count)} ${query ? (count === 1 ? "match" : "matches") : (count === 1 ? "test" : "tests")}`));
const chevron = node("span", "file-chevron", "›"); chevron.setAttribute("aria-hidden", "true"); row.append(chevron); list.append(row);
});
if (!visibleFiles.length) list.append(node("div", "empty", "No test files match this search.")); listPanel.append(list); content.append(listPanel);
}
// Copy without a clipboard API.
//
// A report is opened from a file:// URL, which is not a secure context, so
// navigator.clipboard is usually absent exactly where this button matters.
// The textarea path is the one that runs most of the time, not the fallback.
function copyBySelection(value) {
return new Promise((resolve, reject) => {
const field = document.createElement("textarea");
field.value = value; field.setAttribute("readonly", "");
field.style.position = "fixed"; field.style.opacity = "0";
document.body.append(field); field.select();
let copied = false;
try { copied = document.execCommand("copy"); } catch { copied = false; }
field.remove();
copied ? resolve() : reject(new Error("Copy failed"));
});
}
function copyText(value) {
// The clipboard API also rejects in a secure context when the document
// does not hold focus or permission was refused, and the selection path
// often still works there, so it is tried on any failure rather than only
// when the API is missing.
if (navigator.clipboard && window.isSecureContext) {
return navigator.clipboard.writeText(value).catch(() => copyBySelection(value));
}
return copyBySelection(value);
}
// Local, copyable agent prompts share the public site’s controls and assets.
function agentMarks() {
const marks = node("span", "agent-marks"); marks.setAttribute("aria-hidden", "true");
[
["codex", "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"1.68 1.75 16.65 16.5\"><path d=\"M11.248 18.25q-.825 0-1.568-.314a4.3 4.3 0 0 1-1.32-.874 4 4 0 0 1-1.304.214 4 4 0 0 1-2.046-.544 4.27 4.27 0 0 1-1.518-1.485 4 4 0 0 1-.56-2.095q0-.48.131-1.04A4.4 4.4 0 0 1 2.04 10.71a4.07 4.07 0 0 1 .017-3.4 4.2 4.2 0 0 1 1.056-1.418 3.8 3.8 0 0 1 1.6-.842 3.9 3.9 0 0 1 .76-1.683q.593-.759 1.451-1.188a4.04 4.04 0 0 1 1.832-.429q.825 0 1.567.313.742.314 1.32.875a4 4 0 0 1 1.304-.215q1.106 0 2.046.545a4.14 4.14 0 0 1 1.501 1.485q.578.941.578 2.095 0 .48-.132 1.04.66.61 1.023 1.419.363.792.363 1.666 0 .892-.38 1.717a4.3 4.3 0 0 1-1.072 1.435 3.8 3.8 0 0 1-1.584.825 3.8 3.8 0 0 1-.775 1.683 4.06 4.06 0 0 1-1.436 1.188 4.04 4.04 0 0 1-1.832.429m-4.076-2.062q.825 0 1.435-.347l3.103-1.782a.36.36 0 0 0 .164-.313v-1.42L7.881 14.62a.67.67 0 0 1-.726 0l-3.118-1.798a.5.5 0 0 1-.017.115v.198q0 .841.396 1.551.413.693 1.139 1.089a3.2 3.2 0 0 0 1.617.412m.165-2.69a.4.4 0 0 0 .181.05q.083 0 .165-.05l1.238-.71-3.977-2.31a.7.7 0 0 1-.363-.643v-3.58q-.825.362-1.32 1.122a2.9 2.9 0 0 0-.495 1.65q0 .809.413 1.55.412.743 1.072 1.123zm3.91 3.663q.875 0 1.585-.396a2.96 2.96 0 0 0 1.534-2.64v-3.564a.32.32 0 0 0-.165-.297l-1.254-.726v4.604a.7.7 0 0 1-.363.643l-3.119 1.799a3 3 0 0 0 1.783.577m.627-6.039V8.878L10.01 7.822 8.129 8.878v2.244l1.881 1.056zM7.057 5.859a.7.7 0 0 1 .363-.644l3.119-1.798a3 3 0 0 0-1.782-.578q-.874 0-1.584.396A2.96 2.96 0 0 0 6.05 4.324a3.07 3.07 0 0 0-.396 1.551v3.547q0 .199.165.314l1.237.726zm8.383 7.887q.825-.364 1.303-1.123.495-.758.495-1.65a3.15 3.15 0 0 0-.412-1.55q-.413-.743-1.073-1.123l-3.086-1.782q-.099-.065-.181-.049a.3.3 0 0 0-.165.05l-1.238.692 3.993 2.327a.6.6 0 0 1 .264.264.64.64 0 0 1 .1.363zm-3.317-8.382a.63.63 0 0 1 .726 0l3.135 1.831v-.297q0-.792-.396-1.501a2.86 2.86 0 0 0-1.105-1.155q-.71-.43-1.65-.43-.825 0-1.436.347L8.294 5.941a.36.36 0 0 0-.165.314v1.418z\"/></svg>"],
["claude", "<svg fill=\"#D97757\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><title>Claude</title><path d=\"m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z\"/></svg>"],
["gemini", "<svg fill=\"#8E75B2\" role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><title>Google Gemini</title><path d=\"M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81\"/></svg>"]
].forEach(([brand, svg]) => {
const mark = svgMark(svg); mark.classList.add(`agent-${brand}`); marks.append(mark);
}); return marks;
}
function agentButton(label, accessibleLabel, onClick) {
const action = button("", "subtle-button improve-button", () => onClick(action));
action.setAttribute("aria-label", accessibleLabel); action.setAttribute("aria-haspopup", "dialog");
action.append(agentMarks(), node("span", "", label)); return action;
}
function improveButton(kind, file = null) {
return agentButton("Improve", `Improve ${kind}${file ? ` for ${file}` : ""}`, action => openImprove(kind, file, action));
}
function promptContext(includeQuality = false) {
const run = currentRun(), quality = currentQuality();
let context = `For project ${JSON.stringify(state.bundle.project)}, ${run ? `using Supercov run ${run.id}` : "no Supercov coverage run is recorded yet"}.`;
if (quality && (includeQuality || !run)) context += ` Quality assessment: ${quality.id}.`;
return context;
}
const PROMPT_ICONS = {"claude": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRDk3NzU3IiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+Q2xhdWRlPC90aXRsZT48cGF0aCBkPSJtNC43MTQ0IDE1Ljk1NTUgNC43MTc0LTIuNjQ3MS4wNzktLjIzMDctLjA3OS0uMTI3NWgtLjIzMDdsLS43ODkzLS4wNDg2LTIuNjk1Ni0uMDcyOS0yLjMzNzUtLjA5NzEtMi4yNjQ2LS4xMjE0LS41NzA3LS4xMjE1LS41MzQzLS43MDQyLjA1NDYtLjM1MjIuNDc5Ny0uMzIxOC42ODYuMDYwOCAxLjUxNzkuMTAzMiAyLjI3NjcuMTU3OCAxLjY1MTQuMDk3MiAyLjQ0NjguMjU1aC4zODg2bC4wNTQ2LS4xNTc5LS4xMzM2LS4wOTcxLS4xMDMyLS4wOTcyTDYuOTczIDkuODM1NmwtMi41NS0xLjY4NzktMS4zMzU2LS45NzE0LS43MjI1LS40OTE4LS4zNjQzLS40NjE0LS4xNTc4LTEuMDA3OC42NTU3LS43MjI1Ljg4MDMuMDYwNy4yMjQ2LjA2MDcuODkyNS42ODYgMS45MDY0IDEuNDc1NCAyLjQ4OTMgMS44MzM2LjM2NDMuMzAzNS4xNDU3LS4xMDMyLjAxODItLjA3MjgtLjE2NC0uMjczMy0xLjM1MzktMi40NDY3LTEuNDQ1LTIuNDg5My0uNjQzNS0xLjAzMi0uMTctLjYxOTRjLS4wNjA3LS4yNTUtLjEwMzItLjQ2NzQtLjEwMzItLjcyODVMNi4yODcuMTMzNSA2LjY5OTcgMGwuOTk1Ny4xMzM2LjQxOS4zNjQyLjYxOTIgMS40MTQ3IDEuMDAxOCAyLjIyODIgMS41NTQzIDMuMDI5Ni40NTUzLjg5ODUuMjQyOS44MzE4LjA5MS4yNTVoLjE1Nzl2LS4xNDU3bC4xMjc1LTEuNzA2LjIzNjgtMi4wOTQ3LjIzMDctMi42OTU3LjA3ODktLjc1ODkuMzc2NC0uOTEwNy43NDY4LS40OTE4LjU4MjguMjc5My40Nzk3LjY4Ni0uMDY2OC40NDMzLS4yODUzIDEuODUxNy0uNTU4NiAyLjkwMjEtLjM2NDMgMS45NDI5aC4yMTI1bC4yNDI5LS4yNDI5Ljk4MzUtMS4zMDUzIDEuNjUxNC0yLjA2NDMuNzI4Ni0uODE5Ni44NS0uOTA0Ni41NDY0LS40MzExaDEuMDMyMWwuNzU5IDEuMTI5My0uMzQgMS4xNjU3LTEuMDYyNSAxLjM0NzgtLjg4MDQgMS4xNDE0LTEuMjYyOCAxLjctLjc4OTMgMS4zNi4wNzI5LjEwOTMuMTg4Mi0uMDE4MyAyLjg1MzUtLjYwNyAxLjU0MjEtLjI3OTQgMS44Mzk2LS4zMTU3LjgzMTguMzg4Ni4wOTEuMzk0Ni0uMzI3OC44MDc1LTEuOTY3LjQ4NTctMi4zMDcyLjQ2MTQtMy40MzY0LjgxMzYtLjA0MjUuMDMwNC4wNDg2LjA2MDcgMS41NDgyLjE0NTcuNjYxOC4wMzY0aDEuNjIxbDMuMDE3NS4yMjQ3Ljc4OTIuNTIyLjQ3MzYuNjM3Ni0uMDc5LjQ4NTctMS4yMTQyLjYxOTMtMS42MzkzLS4zODg2LTMuODI1LS45MTA3LTEuMzExMy0uMzI3OWgtLjE4MjJ2LjEwOTNsMS4wOTI5IDEuMDY4NiAyLjAwMzUgMS44MDkyIDIuNTA3NSAyLjMzMTQuMTI3NS41NzY4LS4zMjE4LjQ1NTQtLjM0LS4wNDg2LTIuMjAzOS0xLjY1NzUtLjg1LS43NDY4LTEuOTI0Ni0xLjYyMWgtLjEyNzV2LjE3bC40NDMyLjY0OTYgMi4zNDM2IDMuNTIxNC4xMjE0IDEuMDgwNy0uMTcuMzUyMS0uNjA3MS4yMTI1LS42Njc5LS4xMjE0LTEuMzcyMS0xLjkyNDZMMTQuMzggMTcuOTU5bC0xLjE0MTQtMS45NDI4LS4xMzk3LjA3OS0uNjc0IDcuMjU1Mi0uMzE1Ni4zNzAzLS43Mjg2LjI3OTMtLjYwNzEtLjQ2MTQtLjMyMTgtLjc0NjguMzIxOC0xLjQ3NTMuMzg4Ni0xLjkyNDYuMzE1Ny0xLjUzLjI4NTMtMS45MDA0LjE3LS42MzE0LS4wMTIxLS4wNDI1LS4xMzk3LjAxODItMS40MzI4IDEuOTY3Mi0yLjE3OTYgMi45NDQ2LTEuNzI0MyAxLjg0NTYtLjQxMjguMTY0LS43MTY0LS4zNzA0LjA2NjctLjY2MTguNDAwOC0uNTg4OSAyLjM4Ni0zLjAzNTcgMS40Mzg5LTEuODgyLjkyOS0xLjA4NjgtLjAwNjItLjE1NzloLS4wNTQ2bC02LjMzODUgNC4xMTY0LTEuMTI5My4xNDU3LS40ODU3LS40NTU0LjA2MDgtLjc0NjcuMjMwNy0uMjQyOSAxLjkwNjQtMS4zMTE0WiIvPjwvc3ZnPg==", "codex": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjEuNjggMS43NSAxNi42NSAxNi41Ij48cGF0aCBkPSJNMTEuMjQ4IDE4LjI1cS0uODI1IDAtMS41NjgtLjMxNGE0LjMgNC4zIDAgMCAxLTEuMzItLjg3NCA0IDQgMCAwIDEtMS4zMDQuMjE0IDQgNCAwIDAgMS0yLjA0Ni0uNTQ0IDQuMjcgNC4yNyAwIDAgMS0xLjUxOC0xLjQ4NSA0IDQgMCAwIDEtLjU2LTIuMDk1cTAtLjQ4LjEzMS0xLjA0QTQuNCA0LjQgMCAwIDEgMi4wNCAxMC43MWE0LjA3IDQuMDcgMCAwIDEgLjAxNy0zLjQgNC4yIDQuMiAwIDAgMSAxLjA1Ni0xLjQxOCAzLjggMy44IDAgMCAxIDEuNi0uODQyIDMuOSAzLjkgMCAwIDEgLjc2LTEuNjgzcS41OTMtLjc1OSAxLjQ1MS0xLjE4OGE0LjA0IDQuMDQgMCAwIDEgMS44MzItLjQyOXEuODI1IDAgMS41NjcuMzEzLjc0Mi4zMTQgMS4zMi44NzVhNCA0IDAgMCAxIDEuMzA0LS4yMTVxMS4xMDYgMCAyLjA0Ni41NDVhNC4xNCA0LjE0IDAgMCAxIDEuNTAxIDEuNDg1cS41NzguOTQxLjU3OCAyLjA5NSAwIC40OC0uMTMyIDEuMDQuNjYuNjEgMS4wMjMgMS40MTkuMzYzLjc5Mi4zNjMgMS42NjYgMCAuODkyLS4zOCAxLjcxN2E0LjMgNC4zIDAgMCAxLTEuMDcyIDEuNDM1IDMuOCAzLjggMCAwIDEtMS41ODQuODI1IDMuOCAzLjggMCAwIDEtLjc3NSAxLjY4MyA0LjA2IDQuMDYgMCAwIDEtMS40MzYgMS4xODggNC4wNCA0LjA0IDAgMCAxLTEuODMyLjQyOW0tNC4wNzYtMi4wNjJxLjgyNSAwIDEuNDM1LS4zNDdsMy4xMDMtMS43ODJhLjM2LjM2IDAgMCAwIC4xNjQtLjMxM3YtMS40Mkw3Ljg4MSAxNC42MmEuNjcuNjcgMCAwIDEtLjcyNiAwbC0zLjExOC0xLjc5OGEuNS41IDAgMCAxLS4wMTcuMTE1di4xOThxMCAuODQxLjM5NiAxLjU1MS40MTMuNjkzIDEuMTM5IDEuMDg5YTMuMiAzLjIgMCAwIDAgMS42MTcuNDEybS4xNjUtMi42OWEuNC40IDAgMCAwIC4xODEuMDVxLjA4MyAwIC4xNjUtLjA1bDEuMjM4LS43MS0zLjk3Ny0yLjMxYS43LjcgMCAwIDEtLjM2My0uNjQzdi0zLjU4cS0uODI1LjM2Mi0xLjMyIDEuMTIyYTIuOSAyLjkgMCAwIDAtLjQ5NSAxLjY1cTAgLjgwOS40MTMgMS41NS40MTIuNzQzIDEuMDcyIDEuMTIzem0zLjkxIDMuNjYzcS44NzUgMCAxLjU4NS0uMzk2YTIuOTYgMi45NiAwIDAgMCAxLjUzNC0yLjY0di0zLjU2NGEuMzIuMzIgMCAwIDAtLjE2NS0uMjk3bC0xLjI1NC0uNzI2djQuNjA0YS43LjcgMCAwIDEtLjM2My42NDNsLTMuMTE5IDEuNzk5YTMgMyAwIDAgMCAxLjc4My41NzdtLjYyNy02LjAzOVY4Ljg3OEwxMC4wMSA3LjgyMiA4LjEyOSA4Ljg3OHYyLjI0NGwxLjg4MSAxLjA1NnpNNy4wNTcgNS44NTlhLjcuNyAwIDAgMSAuMzYzLS42NDRsMy4xMTktMS43OThhMyAzIDAgMCAwLTEuNzgyLS41NzhxLS44NzQgMC0xLjU4NC4zOTZBMi45NiAyLjk2IDAgMCAwIDYuMDUgNC4zMjRhMy4wNyAzLjA3IDAgMCAwLS4zOTYgMS41NTF2My41NDdxMCAuMTk5LjE2NS4zMTRsMS4yMzcuNzI2em04LjM4MyA3Ljg4N3EuODI1LS4zNjQgMS4zMDMtMS4xMjMuNDk1LS43NTguNDk1LTEuNjVhMy4xNSAzLjE1IDAgMCAwLS40MTItMS41NXEtLjQxMy0uNzQzLTEuMDczLTEuMTIzbC0zLjA4Ni0xLjc4MnEtLjA5OS0uMDY1LS4xODEtLjA0OWEuMy4zIDAgMCAwLS4xNjUuMDVsLTEuMjM4LjY5MiAzLjk5MyAyLjMyN2EuNi42IDAgMCAxIC4yNjQuMjY0LjY0LjY0IDAgMCAxIC4xLjM2M3ptLTMuMzE3LTguMzgyYS42My42MyAwIDAgMSAuNzI2IDBsMy4xMzUgMS44MzF2LS4yOTdxMC0uNzkyLS4zOTYtMS41MDFhMi44NiAyLjg2IDAgMCAwLTEuMTA1LTEuMTU1cS0uNzEtLjQzLTEuNjUtLjQzLS44MjUgMC0xLjQzNi4zNDdMOC4yOTQgNS45NDFhLjM2LjM2IDAgMCAwLS4xNjUuMzE0djEuNDE4eiIvPjwvc3ZnPg==", "github-copilot": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjMDAwMDAwIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R2l0SHViIENvcGlsb3Q8L3RpdGxlPjxwYXRoIGQ9Ik0yMy45MjIgMTYuOTk3QzIzLjA2MSAxOC40OTIgMTguMDYzIDIyLjAyIDEyIDIyLjAyIDUuOTM3IDIyLjAyLjkzOSAxOC40OTIuMDc4IDE2Ljk5N0EuNjQxLjY0MSAwIDAgMSAwIDE2Ljc0MXYtMi44NjlhLjg4My44ODMgMCAwIDEgLjA1My0uMjJjLjM3Mi0uOTM1IDEuMzQ3LTIuMjkyIDIuNjA1LTIuNjU2LjE2Ny0uNDI5LjQxNC0xLjA1NS42NDQtMS41MTdhMTAuMDk4IDEwLjA5OCAwIDAgMS0uMDUyLTEuMDg2YzAtMS4zMzEuMjgyLTIuNDk5IDEuMTMyLTMuMzY4LjM5Ny0uNDA2Ljg5LS43MTcgMS40NzQtLjk1MkM3LjI1NSAyLjkzNyA5LjI0OCAxLjk4IDExLjk3OCAxLjk4YzIuNzMxIDAgNC43NjcuOTU3IDYuMTY2IDIuMDkzLjU4NC4yMzUgMS4wNzcuNTQ2IDEuNDc0Ljk1Mi44NS44NjkgMS4xMzIgMi4wMzcgMS4xMzIgMy4zNjggMCAuMzY4LS4wMTQuNzMzLS4wNTIgMS4wODYuMjMuNDYyLjQ3NyAxLjA4OC42NDQgMS41MTcgMS4yNTguMzY0IDIuMjMzIDEuNzIxIDIuNjA1IDIuNjU2YS44NDEuODQxIDAgMCAxIC4wNTMuMjJ2Mi44NjlhLjY0MS42NDEgMCAwIDEtLjA3OC4yNTZabS0xMS43NS01Ljk5MmgtLjM0NGE0LjM1OSA0LjM1OSAwIDAgMS0uMzU1LjUwOGMtLjc3Ljk0Ny0xLjkxOCAxLjQ5Mi0zLjUwOCAxLjQ5Mi0xLjcyNSAwLTIuOTg5LS4zNTktMy43ODItMS4yNTlhMi4xMzcgMi4xMzcgMCAwIDEtLjA4NS0uMTA0TDQgMTEuNzQ2djYuNTg1YzEuNDM1Ljc3OSA0LjUxNCAyLjE3OSA4IDIuMTc5IDMuNDg2IDAgNi41NjUtMS40IDgtMi4xNzl2LTYuNTg1bC0uMDk4LS4xMDRzLS4wMzMuMDQ1LS4wODUuMTA0Yy0uNzkzLjktMi4wNTcgMS4yNTktMy43ODIgMS4yNTktMS41OSAwLTIuNzM4LS41NDUtMy41MDgtMS40OTJhNC4zNTkgNC4zNTkgMCAwIDEtLjM1NS0uNTA4Wm0yLjMyOCAzLjI1Yy41NDkgMCAxIC40NTEgMSAxdjJjMCAuNTQ5LS40NTEgMS0xIDEtLjU0OSAwLTEtLjQ1MS0xLTF2LTJjMC0uNTQ5LjQ1MS0xIDEtMVptLTUgMGMuNTQ5IDAgMSAuNDUxIDEgMXYyYzAgLjU0OS0uNDUxIDEtMSAxLS41NDkgMC0xLS40NTEtMS0xdi0yYzAtLjU0OS40NTEtMSAxLTFabTMuMzEzLTYuMTg1Yy4xMzYgMS4wNTcuNDAzIDEuOTEzLjg3OCAyLjQ5Ny40NDIuNTQ0IDEuMTM0LjkzOCAyLjM0NC45MzggMS41NzMgMCAyLjI5Mi0uMzM3IDIuNjU3LS43NTEuMzg0LS40MzUuNTU4LTEuMTUuNTU4LTIuMzYxIDAtMS4xNC0uMjQzLTEuODQ3LS43MDUtMi4zMTktLjQ3Ny0uNDg4LTEuMzE5LS44NjItMi44MjQtMS4wMjUtMS40ODctLjE2MS0yLjE5Mi4xMzgtMi41MzMuNTI5LS4yNjkuMzA3LS40MzcuODA4LS40MzggMS41Nzh2LjAyMWMwIC4yNjUuMDIxLjU2Mi4wNjMuODkzWm0tMS42MjYgMGMuMDQyLS4zMzEuMDYzLS42MjguMDYzLS44OTR2LS4wMmMtLjAwMS0uNzctLjE2OS0xLjI3MS0uNDM4LTEuNTc4LS4zNDEtLjM5MS0xLjA0Ni0uNjktMi41MzMtLjUyOS0xLjUwNS4xNjMtMi4zNDcuNTM3LTIuODI0IDEuMDI1LS40NjIuNDcyLS43MDUgMS4xNzktLjcwNSAyLjMxOSAwIDEuMjExLjE3NSAxLjkyNi41NTggMi4zNjEuMzY1LjQxNCAxLjA4NC43NTEgMi42NTcuNzUxIDEuMjEgMCAxLjkwMi0uMzk0IDIuMzQ0LS45MzguNDc1LS41ODQuNzQyLTEuNDQuODc4LTIuNDk3WiIvPjwvc3ZnPg==", "gemini": "data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjOEU3NUIyIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R29vZ2xlIEdlbWluaTwvdGl0bGU+PHBhdGggZD0iTTExLjA0IDE5LjMyUTEyIDIxLjUxIDEyIDI0cTAtMi40OS45My00LjY4Ljk2LTIuMTkgMi41OC0zLjgxdDMuODEtMi41NVEyMS41MSAxMiAyNCAxMnEtMi40OSAwLTQuNjgtLjkzYTEyLjMgMTIuMyAwIDAgMS0zLjgxLTIuNTggMTIuMyAxMi4zIDAgMCAxLTIuNTgtMy44MVExMiAyLjQ5IDEyIDBxMCAyLjQ5LS45NiA0LjY4LS45MyAyLjE5LTIuNTUgMy44MWExMi4zIDEyLjMgMCAwIDEtMy44MSAyLjU4UTIuNDkgMTIgMCAxMnEyLjQ5IDAgNC42OC45NiAyLjE5LjkzIDMuODEgMi41NXQyLjU1IDMuODEiLz48L3N2Zz4="};
const PROMPT_LANGUAGES = [
{ id: "javascript", name: "JavaScript", command: "npx supercov" },
{ id: "typescript", name: "TypeScript", command: "npx supercov" },
{ id: "python", name: "Python", command: "uvx --from supercov-cli supercov" },
{ id: "ruby", name: "Ruby", command: "supercov", setup: "Install the Ruby gem with gem install supercov if needed." },
{ id: "rust", name: "Rust", command: "supercov", setup: "Install the Rust crate with cargo install supercov if needed." },
{ id: "go", name: "Go", command: "go run github.com/supercorp-ai/supercov/cmd/supercov@latest" },
{ id: "java", name: "Java", command: "supercov", setup: "Use the native Supercov CLI from https://supercov.com/docs/?language=java." },
{ id: "kotlin", name: "Kotlin", command: "supercov", setup: "Use the native Supercov CLI from https://supercov.com/docs/?language=kotlin." }
];
function detectPromptLanguage(run, quality) {
const command = (run?.command || []).map(value => value.replace(/\\/g, "/").split("/").pop().replace(/\.exe$/i, "")).join(" ");
if (/(?:^| )(ruby|rspec|rake|bundle)(?: |$)/.test(command)) return "ruby";
if (/(?:^| )(python[\d.]*|pytest|uv|tox)(?: |$)/.test(command)) return "python";
if (/(?:^| )(cargo|rustc|nextest)(?: |$)/.test(command)) return "rust";
if (/(?:^| )go (test|run)(?: |$)/.test(command)) return "go";
const extensions = { js: "javascript", jsx: "javascript", mjs: "javascript", cjs: "javascript", ts: "typescript", tsx: "typescript", mts: "typescript", cts: "typescript", py: "python", rb: "ruby", rs: "rust", go: "go", java: "java", kt: "kotlin", kts: "kotlin" };
const counts = {};
for (const file of run?.files?.length ? run.files : quality?.files || []) {
const language = extensions[(file.file || file.path || "").split(".").pop()];
if (language) counts[language] = (counts[language] || 0) + 1;
}
const detected = Object.keys(counts).sort((a, b) => counts[b] - counts[a])[0];
if (detected) return detected;
if (/\b(kotlin|kotlinc)\b/.test(command)) return "kotlin";
if (/\b(java|mvn|gradlew?|maven)\b/.test(command)) return "java";
if (/\b(tsx|ts-node|tsc)\b/.test(command)) return "typescript";
return "javascript";
}
const PROMPT_OPTIONS = {
coverage: [
["One test", "Use coverage to add one useful test.", "Use {tool} to add one useful test for uncovered behavior and verify the result."],
["Software factory", "Write tests until coverage reaches 100%.", "Use {tool} to write meaningful tests until coverage reaches 100%."]
],
assertions: [
["Add assertions", "Test behavior that is not asserted yet.", "Improve assertion coverage with {tool}: add tests that check the statements no test would catch breaking (npx supercov runs latest assertions lists them)."],
["Assess assertions", "Work out how much of the code the tests assert.", "Use {tool} to assess assertion coverage (npx supercov runs latest assertions assess), then add tests for the statements that are not asserted."]
],
quality: [
["One refactor", "Improve the strongest concern in one file.", "Use {tool} to address the strongest quality concern in one file, preserve behavior, and reassess."],
["Improve quality", "Work through the main quality concerns.", "Improve code quality with {tool}. Address the main concerns, preserve behavior, and reassess."]
],
security: [
["Review security", "Find and explain verified vulnerabilities.", "Review security findings with {tool} security. Verify the flagged patterns in context."],
["Fix verified issues", "Address supported findings and test the fixes.", "Use {tool} security to review the saved findings, fix verified issues with regression tests, and reassess."]
],
run: [
["New coverage run", "Measure coverage for the current source.", "Measure coverage in a new run with {tool}."],
["Quality and coverage", "Refresh both measurements for this project.", "Measure code quality and coverage in a new run with {tool}."]
]
};
function promptMetric(kind, file) {
const run = currentRun(), quality = currentQuality();
if (kind === "quality") {
const value = file ? qualityRow(quality, file)?.health : quality?.health;
return `Current quality: ${value != null && Number.isFinite(Number(value)) ? `${health(value)} (${band(value)})` : "Not assessed"}`;
}
if (kind === "security") return `Current security: ${securityLabel(file)}${currentSecurity() ? ` (assessment ${currentSecurity().id})` : ""}`;
if (kind === "assertions") {
const counts = run && file ? assertionFileCount(run, file) : null;
const summary = run && assertionData(run)?.summary;
const value = file ? (counts?.total ? counts.asserted / counts.total * 100 : null) : summary?.status === "available" ? summary.statements?.percentage : null;
return `Current assertions: ${value != null && Number.isFinite(Number(value)) ? pct(value) : "Not measured"}`;
}
if (!run) return "Current coverage: Not measured";
if (file) {
const record = run.files.find(value => value.file === file);
if (record?.measurementLimitations) return "Current coverage: Limited measurement";
const lines = run.lines.filter(line => line.file === file);
const value = lines.length ? pct(lines.filter(line => line.covered).length / lines.length * 100) : "Not measured";
const open = record ? missing(record) : 0;
return `Current coverage: ${value}${open ? ` (${integer(open)} obligations left)` : ""}`;
}
const pool = obligationPool(run);
return `Current coverage: ${pool.total ? pct(pool.percentage) : "Not measured"}`;
}
function openImprove(kind, file, trigger) { openAgentPrompt(kind, file, trigger); }
function openNewRun(trigger) { openAgentPrompt("run", null, trigger); }
function openAgentPrompt(kind, file, trigger) {
const run = currentRun(), quality = currentQuality();
const language = detectPromptLanguage(run, quality || currentSecurity());
let selected = 0, prompt = "";
const content = node("div", "improve-content");
const step = (number, heading) => {
const section = node("section", `prompt-modal-step${number === 1 ? " prompt-modal-step-first" : ""}`);
const head = node("div", "prompt-modal-step-head"); head.append(node("span", "", String(number)), node("h2", "", heading));
section.append(head); content.append(section); return section;
};
const choices = node("fieldset", "prompt-modal-choices"); choices.append(node("legend", "sr-only", "Prompt"));
const rows = PROMPT_OPTIONS[kind].map(([label, description], index) => {
const row = node("label", "prompt-modal-choice"); const input = node("input"); input.type = "radio"; input.name = "supercov-prompt"; input.value = String(index);
const text = node("span"); text.append(node("strong", "", label), node("small", "", description)); row.append(input, text);
input.addEventListener("change", () => { if (input.checked) { selected = index; update(); } }); choices.append(row); return { row, input };
});
step(1, "Choose a prompt").append(choices);
const card = node("div", "prompt-modal-copy-card"); const preview = node("div", "prompt-modal-preview improve-prompt");
const copyIcon = '<svg viewBox="0 0 20 20" aria-hidden="true"><rect x="7" y="3" width="10" height="10" rx="2"/><path d="M13 13v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h2"/></svg>';
const checkIcon = '<svg viewBox="0 0 20 20" aria-hidden="true"><path d="m4 10 4 4 8-8"/></svg>';
const error = node("p", "prompt-copy-error"); error.setAttribute("role", "alert"); error.hidden = true;
const copyLabel = node("span", "", "Copy prompt"), copyMark = svgMark(copyIcon);
const copy = button("", "prompt-modal-copy", async () => {
const copied = prompt;
try {
await copyText(copied);
if (copied !== prompt) return;
copyLabel.textContent = "Copied"; copyMark.innerHTML = checkIcon; copy.classList.add("is-copied");
} catch (_) { error.hidden = false; error.textContent = "Copy failed. Select the prompt and copy it manually."; }
});
copy.append(copyMark, copyLabel); card.append(preview, copy, error); step(2, "Copy prompt").append(card);
const last = step(3, "Open your coding agent");
last.append(node("p", "prompt-modal-instruction", "Start a new task in the repository, paste the prompt, and send."));
const agents = node("div", "prompt-modal-agents");
for (const [id, name] of [["claude", "Claude"], ["codex", "Codex"], ["github-copilot", "Copilot"], ["gemini", "Gemini"]]) {
const entry = node("span"), icon = node("img"); icon.src = PROMPT_ICONS[id]; icon.alt = "";
entry.append(icon, node("small", "", name)); agents.append(entry);
}
last.append(agents);
const update = () => {
const tool = PROMPT_LANGUAGES.find(value => value.id === language);
const metrics = kind === "run" ? (selected === 1 ? ["coverage", "assertions", "quality"] : ["coverage", "assertions"]) : [kind];
const baseline = metrics.map(metric => promptMetric(metric, file)).join(". ") + ".";
const context = `${promptContext(kind === "quality" || (kind === "run" && selected > 0))}${file ? ` Focus on ${file}.` : ""} ${baseline} `;
const [before, after] = PROMPT_OPTIONS[kind][selected][2].split("{tool}");
const reuse = run && kind === "run" ? " Use the saved run’s test command." : "";
const setup = tool.setup ? ` ${tool.setup}` : "";
prompt = `${context}${before}${tool.command}${after}${reuse}${setup}`;
preview.replaceChildren(document.createTextNode(context), document.createTextNode(before), node("code", "", tool.command), document.createTextNode(after + reuse + setup));
rows.forEach(({ row, input }, index) => { input.checked = index === selected; row.classList.toggle("is-selected", input.checked); });
copyLabel.textContent = "Copy prompt"; copyMark.innerHTML = copyIcon; copy.classList.remove("is-copied"); error.hidden = true; error.textContent = "";
};
update(); openReportModal("agent", run, trigger, { title: kind === "run" ? "New run" : `Improve ${kind}`, content });
}
function missingMetric(kind, label, message) {
const metric = node("div", "overview-metric"); const heading = node("div", "metric-heading");
heading.append(node("h2", "overview-label", label), improveButton(kind)); metric.append(heading);
const head = node("div", "instrument-head"); head.append(kind === "coverage" ? markCoverage(null, 62) : kind === "assertions" ? markCredit(null, 62) : markQuality(null, 62));
const stack = node("div", "figure-stack"); const figure = node("div", "figure"); figure.append(node("span", "figure-number quiet", "—"));
stack.append(figure, node("div", "figure-unit", kind === "quality" ? "Not assessed" : "Not measured")); head.append(stack); metric.append(head);
if (message) metric.title = message; return metric;
}
function searchableEvidence(items, makeRow, searchableText, placeholder) {
const content = node("div", "modal-evidence");
const search = node("input", "modal-search"); search.type = "search"; search.placeholder = placeholder; search.setAttribute("aria-label", placeholder.replace("…", ""));
const count = node("p", "modal-description"); count.setAttribute("role", "status");
const list = node("div", "evidence-list");
const update = () => {
const query = search.value.trim().toLowerCase();
const visible = items.filter(item => searchableText(item).toLowerCase().includes(query));
count.textContent = `${integer(visible.length)} of ${integer(items.length)}`;
list.replaceChildren(...visible.map(makeRow));
if (!visible.length) list.append(node("p", "relation-empty", "No matches. Try another search."));
};
search.addEventListener("input", update); content.append(search, count, list); update(); return content;
}
function openReportModal(kind, run, trigger, evidence, file = null) {
const dialog = node("dialog", "report-modal"); dialog.setAttribute("aria-labelledby", "report-modal-title");
const heading = node("div", "report-modal-head"); const title = node("h2", "", evidence?.title || (kind === "command" ? "Command" : "Coverage breakdown")); title.id = "report-modal-title"; title.tabIndex = -1; title.setAttribute("autofocus", "");
const close = button("", "report-modal-close", () => dialog.close()); close.setAttribute("aria-label", "Close dialog");
const closeIcon = svgMark('<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>'); closeIcon.setAttribute("aria-hidden", "true"); close.append(closeIcon);
heading.append(title, close); dialog.append(heading);
if (evidence) { dialog.classList.add(kind === "agent" ? "prompt-modal" : "evidence-modal"); if (kind === "agent") close.classList.add("prompt-modal-close"); dialog.append(evidence.content); }
else if (kind === "command") {
// Quote only for display. Stored commands are never evaluated.
const quote = value => /^[a-zA-Z0-9_./:=@+-]+$/.test(value) ? value : "'" + value.replace(/'/g, "'\"'\"'") + "'";
const command = run.command.map(quote).join(" ");
dialog.append(node("p", "modal-description", "The command used for this run."), node("pre", "modal-command", command));
} else {
const pool = file ? fileObligationPool(run, file) : coverageBreakdownPool(run);
if (file) dialog.append(filePath(file, "breakdown-file"));
dialog.append(renderObligations(run, pool, file));
}
const release = () => { document.body.classList.remove("report-modal-open"); dialog.remove(); if (trigger.isConnected) trigger.focus({ preventScroll: true }); };
dialog.addEventListener("close", release, { once: true });
dialog.addEventListener("keydown", event => {
if (event.key === "Escape") { event.preventDefault(); dialog.close(); return; }
if (event.key !== "Tab") return;
const controls = [...dialog.querySelectorAll('button:not([disabled]), a[href], input, select, textarea, [tabindex="0"]')];
const first = controls[0], last = controls[controls.length - 1];
if (event.shiftKey && (document.activeElement === first || document.activeElement === title)) { event.preventDefault(); last.focus(); }
else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
});
dialog.addEventListener("click", event => {
if (event.target !== dialog) return;
const rect = dialog.getBoundingClientRect();
if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) dialog.close();
});
document.body.classList.add("report-modal-open"); app.append(dialog); dialog.showModal(); title.focus({ preventScroll: true });
}
// A judgment this item does not carry, said plainly, with the way to get it.
function renderMissing(kind, heading, copy, instrument) {
const metricKind = kind === "run" ? "coverage" : kind;
if (instrument) { const card = node("section", "instrument overview-card"); card.append(missingMetric(metricKind, metricKind === "quality" ? "Quality" : "Assertions", copy)); return card; }
const card = node("div", "missing-card"); card.append(node("div", "missing-heading", heading), node("div", "missing-copy", copy));
if (kind) card.append(improveButton(metricKind, state.file)); return card;
}
let renderedView = null;
function render() {
document.querySelector("dialog.report-modal[open]")?.close();
const view = JSON.stringify([state.itemId, state.tab, state.file, state.testId, state.testSuite, state.testFileFilter]);
const changedView = renderedView !== null && renderedView !== view;
renderedView = view;
const run = currentRun();
const quality = currentQuality();
setHash(); app.replaceChildren();
const layout = node("div", "report-layout"); renderSidebar(layout);
const shell = node("main", "report-main"); renderTabs(shell);
if (!run) {
if (state.tab === "review") renderQualityOnly(shell, quality);
else {
const content = node("div", "report-content standalone"); content.setAttribute("role", "tabpanel");
if (state.tab === "explore" && quality) renderQualityFiles(content, quality);
else if (state.tab === "explore" && currentSecurity()) renderSecurityFiles(content);
else content.append(node("div", "empty", "This entry has no coverage run, so there are no tests to show."));
shell.append(content);
}
} else if (state.tab === "review") renderHero(shell, run);
else { const content = node("div", "report-content standalone"); content.setAttribute("role", "tabpanel"); if (state.tab === "explore") renderExplore(content, run); else renderTests(content, run); shell.append(content); }
layout.append(shell); app.append(layout);
syncThemeUi();
if (changedView && !restoringHash) window.scrollTo(0, 0);
}
async function load() {
try {
try { const theme = localStorage.getItem("supercov-report-theme"); if (theme === "light" || theme === "dark") storedTheme = theme; } catch {}
applyTheme(); systemTheme.addEventListener("change", () => { applyTheme(); syncThemeUi(); });
window.addEventListener("popstate", () => { restoringHash = true; try { readHash(); render(); } finally { restoringHash = false; } });
if (typeof DecompressionStream !== "function") throw new Error("This browser cannot decompress portable reports. Use a current Chrome, Edge, Firefox, or Safari release.");
const binary = atob(payload); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
const bundle = await new Response(stream).json();
// A report may carry assessments and no runs, so the timeline is what
// must not be empty.
if (bundle.schemaVersion !== 2 || !Array.isArray(bundle.timeline) || !bundle.timeline.length) throw new Error("The embedded report data is incompatible or empty.");
state.bundle = bundle;
state.itemId = bundle.selectedId;
state.runId = bundle.selectedRunId || null;
state.compareId = bundle.comparisonRunId || null;
readHash();
const item = currentItem();
state.itemId = item.id;
state.runId = item.runId || null;
if (state.runId && (state.runId !== bundle.selectedRunId || state.compareId === state.runId)) state.compareId = previousRunId(state.runId);
if (!state.runId) state.compareId = null;
render();
} catch (error) {
app.replaceChildren(); const fatal = node("div", "fatal"); fatal.append(node("h1", "", "Could not open this report"), node("p", "muted", error instanceof Error ? error.message : String(error)), node("p", "small muted", "The file stays private and no network request was attempted.")); app.append(fatal);
}
}
systemTheme.addEventListener("change", syncThemeUi);
load();
})();
</script>
</body>
</html>