// 0.32.1 regression: restore pre-0.32.0 content-driven tile heights and // align inter-tile gaps with the same spacing as other Dashboard sections. // These are source/CSS contracts, not a browser or authenticated-UI test. import assert from 'node:assert/strict'; import fs from 'node:fs'; const css = fs.readFileSync('src/admin/web/style.css', 'utf8'); const html = fs.readFileSync('src/admin/web/index.html', 'utf8'); function rule(selector) { // Target precise blocks rather than accepting an unrelated matching rule. const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const match = css.match(new RegExp(`${escaped}\\s*\\{([^{}]*)\\}`)); assert(match, `Missing CSS rule: ${selector}`); return match[1]; } for (const density of ['normal', 'compact', 'ultra']) { const declarations = rule(`html[data-dashboard-density="${density}"] #panel-overview .stats-grid`); assert.match(declarations, /gap:\s*var\(--ui-gap\)\s*;/, `${density} tiles must use the same density-aware gap as Dashboard sections`); } const sharedGap = rule('.overview-grid,\n.diagnostics-grid,\n.settings-grid,\n.config-columns'); assert.match(sharedGap, /gap:\s*var\(--ui-gap\)/); for (const density of ['dense', 'comfortable', 'compact']) { assert.match(css, new RegExp(`body\\[data-density="${density}"\\]\\s*\\{[^}]*--ui-gap:`), `UI density ${density} must provide the shared gap variable`); } assert.match(css, /@media \(min-width: 1201px\)\s*\{\s*#panel-overview \.stats-grid\s*\{\s*grid-template-columns:\s*repeat\(6, minmax\(0, 1fr\)\);/, 'six desktop columns are preserved'); assert.doesNotMatch(css, /#panel-overview \.stats-grid\s*>\s*\.stat\s*\{[^}]*\bmin-height\s*:/s, 'desktop cards should grow naturally to their legend/content height'); assert.doesNotMatch(css, /#panel-overview \.stats-grid \.stat:is\(:hover, :focus-visible, :focus-within\)/, 'card descriptions must remain visible, not hover-only'); const heading = rule('html[data-dashboard-density="compact"] #panel-overview .stat>span'); const metric = rule('html[data-dashboard-density="compact"] #panel-overview .stat strong'); const caption = rule('html[data-dashboard-density="compact"] #panel-overview .stat small'); const payload = rule('html[data-dashboard-density="compact"] #panel-overview .payload-split'); assert.match(heading, /font-size:\s*11px/); assert.match(metric, /font-size:\s*25px/); assert.match(caption, /font-size:\s*10px/); assert.match(payload, /font-size:\s*10px\s*!important/); assert.match(css, /#panel-overview \.stats-grid \.stat-chart>\.stat-chart-legend\s*\{[^}]*flex-wrap:\s*wrap/s); const overview = html.slice(html.indexOf('id="panel-overview"'), html.indexOf('id="panel-vhosts"')); const widgets = [...overview.matchAll(/data-dashboard-widget="([^"]+)"/g)].map(match => match[1]); assert.equal(widgets.length, 62, '49 existing widgets plus 6 small and 7 large opt-in charts'); assert.equal(widgets.filter(id => !id.startsWith('backend_') && !id.startsWith('extra_') && !id.startsWith('wide_') && !['traffic_chart','system_chart','connection_chart','cache_diagnostics','process_diagnostics','network_diagnostics','backend_table'].includes(id)).length, 16, 'all 16 pre-existing metric/Stream cards must be retained'); assert.match(overview, /id="metric-bytes-split"/); assert.match(overview, /id="http-protocol-chart"/); assert.match(overview, /id="ws-protocol-chart"/); assert.match(overview, /id="dashboard-overview-grid"/); assert.doesNotMatch(overview, /class="overview-grid"|class="diagnostics-grid"/, 'all Overview widgets must share one arrangement grid'); console.log('PASS: 0.32.1 Overview six-column content height, 90% compact typography, shared tile spacing and inline legends.');