diff --git a/media/webview.css b/media/webview.css
index 122d4fa..32d26e8 100644
--- a/media/webview.css
+++ b/media/webview.css
@@ -70,6 +70,7 @@ button { font-family: inherit; color: inherit; background: none; border: none; c
#dashboard {
padding: 24px 28px 40px;
max-width: 1400px;
+ min-width: 500px;
}
/* ── Dashboard header ────────────────────────────────────────────────────────── */
@@ -212,6 +213,7 @@ button { font-family: inherit; color: inherit; background: none; border: none; c
display: flex;
align-items: center;
gap: 10px;
+ min-width: 0;
}
.kpi-sev-bar { width: 3px; align-self: stretch; border-radius: 2px; flex-shrink: 0; }
@@ -257,6 +259,7 @@ button { font-family: inherit; color: inherit; background: none; border: none; c
border: 1px solid var(--border);
border-radius: var(--r-md);
padding: 18px 20px;
+ min-width: 200px;
}
.card-header {
@@ -894,7 +897,7 @@ button { font-family: inherit; color: inherit; background: none; border: none; c
.trend-card-stripe { display:flex; align-items:center; gap:16px; }
.trend-card-stripe-bar { width:3px; align-self:stretch; border-radius:2px; flex-shrink:0; }
-.trend-chart-area { width:100%; }
+.trend-chart-area { width:100%; overflow:hidden; min-width:0; }
.nf-legend { display:flex; gap:16px; margin-top:8px; font-size:11px; color:var(--fg-muted); }
.nf-legend-dot { width:12px; height:2px; border-radius:1px; display:inline-block; margin-right:4px; vertical-align:middle; }
diff --git a/media/webview.js b/media/webview.js
index afc9663..3c7229a 100644
--- a/media/webview.js
+++ b/media/webview.js
@@ -55,6 +55,7 @@ let config = {
showCheckNameChart: true,
showSourceChart: true,
showFileChart: true,
+ maxChartSnapshots: 20,
/** @type {Array<{name:string,index:number}>} */
customColumns: [],
};
@@ -75,6 +76,8 @@ const snippetMeta = new Map();
let currentView = 'overview';
let navTabsReady = false;
+/** @type {ResizeObserver|null} */
+let trendsResizeObserver = null;
function setView(view) {
currentView = view;
@@ -1035,8 +1038,8 @@ function getChartTooltip() {
return tip;
}
-/** @param {HTMLElement} wrapEl @param {number} n @param {number} svgW @param {number} PL @param {number} PR @param {(idx:number)=>string} getContent */
-function wireChartHover(wrapEl, n, svgW, PL, PR, getContent) {
+/** @param {HTMLElement} wrapEl @param {number} n @param {number} _svgW @param {number} PL @param {number} PR @param {(idx:number)=>string} getContent */
+function wireChartHover(wrapEl, n, _svgW, PL, PR, getContent) {
const tip = getChartTooltip();
wrapEl.style.position = 'relative';
const ch = document.createElement('div');
@@ -1044,12 +1047,16 @@ function wireChartHover(wrapEl, n, svgW, PL, PR, getContent) {
wrapEl.appendChild(ch);
wrapEl.addEventListener('mousemove', e => {
- const rect = wrapEl.getBoundingClientRect();
- const scale = rect.width / svgW;
- const relX = (e.clientX - rect.left) / scale;
- const frac = Math.max(0, Math.min(1, (relX - PL) / (svgW - PL - PR)));
+ const svgEl = wrapEl.querySelector('svg');
+ if (!svgEl) return;
+ const svgRect = svgEl.getBoundingClientRect();
+ const wrapRect = wrapEl.getBoundingClientRect();
+ const W = svgRect.width;
+ const relX = e.clientX - svgRect.left;
+ const frac = Math.max(0, Math.min(1, (relX - PL) / (W - PL - PR)));
const idx = Math.max(0, Math.min(n - 1, Math.round(frac * (n - 1))));
- ch.style.left = ((PL + frac * (svgW - PL - PR)) * scale).toFixed(1) + 'px';
+ const xInSvg = n < 2 ? PL + (W - PL - PR) / 2 : PL + (idx / (n - 1)) * (W - PL - PR);
+ ch.style.left = (svgRect.left - wrapRect.left + xInSvg).toFixed(1) + 'px';
ch.style.display = 'block';
tip.innerHTML = getContent(idx);
tip.style.display = 'block';
@@ -1063,10 +1070,10 @@ function wireChartHover(wrapEl, n, svgW, PL, PR, getContent) {
wrapEl.addEventListener('mouseleave', () => { tip.style.display = 'none'; ch.style.display = 'none'; });
}
-function buildNewFixedSvg(pts) {
+function buildNewFixedSvg(pts, W = 560) {
// pts: [{new, fixed}] chronologically (snaps[0..n-1] + current)
if (pts.length < 2) return '';
- const W = 560, H = 120, PL = 36, PR = 12, PT = 10, PB = 20;
+ const H = 120, PL = 36, PR = 12, PT = 10, PB = 20;
const cW = W - PL - PR, cH = H - PT - PB;
const maxVal = Math.max(...pts.flatMap(p => [p.new, p.fixed]), 1);
const xOf = /** @param {number} i */ i => PL + (pts.length < 2 ? cW / 2 : (i / (pts.length - 1)) * cW);
@@ -1083,7 +1090,7 @@ function buildNewFixedSvg(pts) {
${lbl}`;
}).join('');
- return `