`,
+ } {
+ if !strings.Contains(html, snippet) {
+ t.Fatalf("Overview window toolbar missing snippet %q", snippet)
+ }
+ }
+ for _, banned := range []string{
+ `.overview-kpi-range { position: absolute;`,
+ `.overview-kpis { position: relative;`,
+ } {
+ if strings.Contains(html, banned) {
+ t.Fatalf("Overview KPI window controls must be in normal flow, found %q", banned)
+ }
+ }
+ render := jsFunc(t, html, "renderOverviewKPIs")
+ toolbar := strings.Index(render, "${overviewKPIWindowControls()}")
+ grid := strings.Index(render, `
`)
+ if toolbar < 0 || grid < 0 || toolbar > grid {
+ t.Fatalf("Overview KPI toolbar must render before the tile grid: toolbar=%d grid=%d", toolbar, grid)
+ }
+}
+
+func TestOverviewWindowControlsPreserveBehavior(t *testing.T) {
+ node, err := exec.LookPath("node")
+ if err != nil {
+ t.Skip("node unavailable: Overview window controls were not executed")
+ }
+ html := indexHTML(t)
+ script := `const assert = require('node:assert/strict');
+const OVERVIEW_KPI_WINDOWS = { '24h': 24 * 3600e3, '7d': 7 * 24 * 3600e3 };
+let _overviewKPIWindow = '7d';
+function esc(v) { return String(v).replace(/[&<>"]/g, ch => ({'&':'&','<':'<','>':'>','"':'"'}[ch])); }
+` + jsFunc(t, html, "overviewKPIWindowControls") + `
+const out = overviewKPIWindowControls();
+assert.match(out, /class="gov-pr-models-toggle window-pills-toolbar overview-window-toolbar"/);
+assert.match(out, /data-stop="1"/);
+assert.match(out, /data-action="setOverviewKPIWindow"/);
+assert.match(out, /data-arg0="24h"[^>]*>24h<\/button>/);
+assert.match(out, /class="active"[^>]*data-action="setOverviewKPIWindow"[^>]*data-arg0="7d"/);
+assert.match(out, /aria-pressed="true"[^>]*>7d<\/button>/);
+`
+ out, err := exec.Command(node, "-e", script).CombinedOutput()
+ if err != nil {
+ t.Fatalf("node Overview window controls failed: %v\n%s", err, strings.TrimSpace(string(out)))
+ }
+}
diff --git a/src/pkg/dashboard/pr_throughput_ui_test.go b/src/pkg/dashboard/pr_throughput_ui_test.go
index e6cc5f0ed8..7c2107f439 100644
--- a/src/pkg/dashboard/pr_throughput_ui_test.go
+++ b/src/pkg/dashboard/pr_throughput_ui_test.go
@@ -94,8 +94,8 @@ func TestPRThroughputCollapsedHeadlineKeepsWindowPill(t *testing.T) {
}
html := indexHTML(t)
for _, snippet := range []string{
- `.prt-window-controls { grid-column: 1 / -1; justify-self: end; max-width: 100%; flex-wrap: wrap; justify-content: flex-end; }`,
- `.prt-window-controls { justify-self: start; justify-content: flex-start; }`,
+ `.window-pills-toolbar { grid-column: 1 / -1; justify-self: end; max-width: 100%; flex-wrap: wrap; justify-content: flex-end; }`,
+ `.window-pills-toolbar { justify-self: start; justify-content: flex-start; }`,
`.prt-share-tile .prt-role-select { position:absolute; right:var(--sp-3); bottom:var(--sp-3);`,
`.prt-share-tile .prt-tile-spark { max-width: calc(100% - 6.4rem); overflow: hidden; }`,
} {
@@ -126,7 +126,7 @@ const PRT_WINDOWS = [[1, '1h'], [6, '6h'], [12, '12h'], [24, '24h'], [48, '48h']
let prtHours = 168;
` + jsFunc(t, html, "prtWindowControls") + `
const out = prtWindowControls();
-assert.match(out, /class="gov-pr-models-toggle prt-window-controls"/);
+assert.match(out, /class="gov-pr-models-toggle window-pills-toolbar prt-window-controls"/);
assert.match(out, /data-stop="1"/);
assert.match(out, /data-action="setPRThroughputHours"/);
assert.match(out, /data-arg0="168"[^>]*>7d<\/button>/);
diff --git a/src/pkg/dashboard/static/index.html b/src/pkg/dashboard/static/index.html
index 072a36da66..a28a66d156 100644
--- a/src/pkg/dashboard/static/index.html
+++ b/src/pkg/dashboard/static/index.html
@@ -1427,7 +1427,7 @@
.prt-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: var(--sp-4); }
.prt-controls { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.prt-headline { position: relative; }
- .prt-window-controls { grid-column: 1 / -1; justify-self: end; max-width: 100%; flex-wrap: wrap; justify-content: flex-end; }
+ .window-pills-toolbar { grid-column: 1 / -1; justify-self: end; max-width: 100%; flex-wrap: wrap; justify-content: flex-end; }
.prt-repo-select { max-width: 220px; background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: var(--r-sm); padding: var(--sp-1) var(--sp-3); font-size: var(--fs-xs); }
.lc-fleet-stat .val.opened { color: var(--blue); }
.lc-fleet-stat .val.closed { color: var(--red); }
@@ -1473,7 +1473,7 @@
.prt-caption { color:var(--muted); font-size:var(--fs-xs); margin-top:var(--sp-2); }
@media (max-width: 800px) { .prt-attribution { grid-template-columns:1fr; } }
@media (max-width: 900px) { .lc-fleet.prt-headline { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
- @media (max-width: 640px) { .lc-fleet.prt-headline { grid-template-columns: 1fr; } .prt-window-controls { justify-self: start; justify-content: flex-start; } }
+ @media (max-width: 640px) { .lc-fleet.prt-headline { grid-template-columns: 1fr; } .window-pills-toolbar { justify-self: start; justify-content: flex-start; } }
.lc-journeys { list-style: none; margin: var(--sp-0); padding: var(--sp-0); display: flex; flex-direction: column; gap: var(--sp-2);
max-height: 420px; overflow-y: auto; }
.lc-journey { font-size: 0.78rem; border-radius: var(--r); background: var(--bg); border: 1px solid var(--border); overflow: hidden; }
@@ -1784,8 +1784,9 @@
.overview-settings-field input, .overview-settings-field select, .overview-chart-select { min-width: 0; border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--bg); color: var(--text); padding: var(--sp-1) var(--sp-2); font: inherit; }
.overview-repo-filter { display: grid; gap: var(--sp-2); max-height: min(16rem, 32vh); overflow: auto; padding: var(--sp-2); border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--bg); }
.overview-settings-links { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
- .overview-kpis { position: relative; display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: var(--sp-3); margin-bottom: var(--sp-4); padding-top: var(--sp-6); }
- .overview-kpi-range { position: absolute; top: 0; right: 0; display: inline-flex; gap: var(--sp-1); }
+ .overview-headline { display: grid; gap: var(--sp-3); margin-bottom: var(--sp-4); }
+ .overview-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: var(--sp-3); }
+ .overview-kpi-range { display: inline-flex; gap: var(--sp-1); }
.overview-kpi-range-btn { min-width: 2rem; height: 1.25rem; border: 1px solid var(--border); border-radius: var(--r-pill); background: var(--bg); color: var(--muted); font-size: var(--fs-2xs); font-weight: 800; cursor: pointer; }
.overview-kpi-range-btn.active { border-color: var(--accent); color: var(--accent); }
.overview-kpi { display: grid; gap: var(--sp-1); text-align: left; border: 1px solid var(--border); border-radius: var(--r); background: var(--bg); color: var(--text); padding: var(--sp-3); min-height: 5.9rem; cursor: pointer; }
@@ -13279,7 +13280,7 @@
What sh
})}`;
}
function overviewKPIWindowControls() {
- return `${Object.keys(OVERVIEW_KPI_WINDOWS).map(win => ``).join('')}`;
+ return `
${Object.keys(OVERVIEW_KPI_WINDOWS).map(win => ``).join('')}
`;
}
function saveOverviewChartsState(state) {
@@ -13627,13 +13628,13 @@ What sh
{ key: 'median-age', field: 'overviewMedianAgeSec', label: `Median actionable age (${state.timeBasis === 'created' ? 'created' : 'updated'})`, value: age, rawValue: ageSec, color: 'var(--purple)', sparkFmt: fmtDurationFromSeconds, tip: `Median time since ${state.timeBasis === 'created' ? 'created' : 'updated'} for actionable open issues and PRs in the selected repos (not MTTR, which measures time-to-resolve for closed items).` }
];
overviewKPIRecordLocal(overviewKPICurrentSample(cards));
- return `${overviewKPIWindowControls()}${cards.map(card => {
+ return `
${overviewKPIWindowControls()}
${cards.map(card => {
const value = card.value;
const n = Number(card.rawValue);
const attrs = Number.isFinite(n) ? ` data-overview-kpi-key="${esc(card.key)}" data-overview-kpi-value="${n}" data-overview-kpi-display="${esc(String(value ?? '—'))}"` : '';
const subline = card.subline ? `${esc(card.subline)}` : '';
return ``;
- }).join('')}
`;
+ }).join('')}
`;
}
function overviewRepoFilterHTML(repos) {
@@ -17452,7 +17453,7 @@ What sh
}
function prtWindowControls() {
const buttons = PRT_WINDOWS.map(([h, label]) => ``).join('');
- return `
${buttons}
`;
+ return `${buttons}
`;
}
function renderPRThroughput(dto) {