This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* Rendered visual contract for AI observability pages.
|
||||
*
|
||||
* This complements the static template guard by checking computed CSS in a
|
||||
* browser: typography, warm-token surfaces, radius, hero backgrounds, and
|
||||
* browser: typography, warm-token surfaces, dot-matrix texture, radius, and
|
||||
* mobile density across the 10 observability pages.
|
||||
*/
|
||||
|
||||
@@ -195,6 +195,11 @@ function contrastRatio(fg, bg) {
|
||||
return (lighter + 0.05) / (darker + 0.05);
|
||||
}
|
||||
|
||||
function isDotMatrixBackground(backgroundImage) {
|
||||
const value = String(backgroundImage || '');
|
||||
return value.includes('radial-gradient') && !value.includes('linear-gradient');
|
||||
}
|
||||
|
||||
async function collectMetrics(page) {
|
||||
return page.evaluate(({ heroSelectors, titleSelectors, surfaceSelectors }) => {
|
||||
const parseRgbValue = (value) => {
|
||||
@@ -234,6 +239,7 @@ async function collectMetrics(page) {
|
||||
const title = document.querySelector(titleSelectors);
|
||||
const badRadius = [];
|
||||
const badBackground = [];
|
||||
const missingMatrix = [];
|
||||
const badChipContrast = [];
|
||||
|
||||
for (const el of document.querySelectorAll(`.momo-observability-mode :is(${surfaceSelectors})`)) {
|
||||
@@ -246,13 +252,24 @@ async function collectMetrics(page) {
|
||||
text: String(el.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 80),
|
||||
});
|
||||
}
|
||||
if ((el.matches(heroSelectors) || el.matches('.obs-panel,.agent-panel,.biz-panel,.runtime-panel,.calls-panel,.gov-panel,.gate-panel,.rag-panel,.qa-panel,.quality-panel,.ppt-panel')) && style.backgroundImage !== 'none') {
|
||||
const mustUseMatrix = el.matches(heroSelectors)
|
||||
|| el.matches('.obs-panel,.agent-panel,.biz-panel,.runtime-panel,.calls-panel,.gov-panel,.gate-panel,.rag-panel,.qa-panel,.quality-panel,.ppt-panel')
|
||||
|| el.matches('.obs-signal,.agent-signal,.biz-signal,.runtime-signal,.calls-signal,.gov-signal,.gate-signal,.rag-signal,.qa-signal,.quality-signal,.ppt-signal');
|
||||
const hasRadialMatrix = style.backgroundImage.includes('radial-gradient');
|
||||
const hasLegacyGradient = style.backgroundImage.includes('linear-gradient');
|
||||
if (mustUseMatrix && (!hasRadialMatrix || hasLegacyGradient)) {
|
||||
badBackground.push({
|
||||
className: String(el.className || '').slice(0, 100),
|
||||
backgroundImage: style.backgroundImage.slice(0, 100),
|
||||
});
|
||||
}
|
||||
if (badRadius.length >= 8 && badBackground.length >= 8) break;
|
||||
if (mustUseMatrix && !hasRadialMatrix) {
|
||||
missingMatrix.push({
|
||||
className: String(el.className || '').slice(0, 100),
|
||||
text: String(el.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 80),
|
||||
});
|
||||
}
|
||||
if (badRadius.length >= 8 && badBackground.length >= 8 && missingMatrix.length >= 8) break;
|
||||
}
|
||||
|
||||
for (const el of document.querySelectorAll('.momo-observability-mode :is(.badge,.obs-pill,[class$="-pill"],.biz-badge)')) {
|
||||
@@ -292,6 +309,7 @@ async function collectMetrics(page) {
|
||||
} : null,
|
||||
badRadius,
|
||||
badBackground,
|
||||
missingMatrix,
|
||||
badChipContrast,
|
||||
};
|
||||
}, { heroSelectors: HERO_SELECTORS, titleSelectors: TITLE_SELECTORS, surfaceSelectors: SURFACE_SELECTORS });
|
||||
@@ -323,13 +341,16 @@ function issuesFor(metrics, viewport) {
|
||||
issues.push('missing hero selector');
|
||||
} else {
|
||||
if (px(metrics.hero.radius) > 8.5) issues.push(`hero radius ${metrics.hero.radius}`);
|
||||
if (metrics.hero.backgroundImage !== 'none') issues.push('hero background image is not none');
|
||||
if (!isDotMatrixBackground(metrics.hero.backgroundImage)) {
|
||||
issues.push('hero missing tokenized dot-matrix background');
|
||||
}
|
||||
if (metrics.hero.height > viewport.maxHeroHeight) {
|
||||
issues.push(`hero too tall ${metrics.hero.height}px > ${viewport.maxHeroHeight}px`);
|
||||
}
|
||||
}
|
||||
if (metrics.badRadius.length) issues.push(`surface radius offenders ${metrics.badRadius.length}`);
|
||||
if (metrics.badBackground.length) issues.push(`surface background-image offenders ${metrics.badBackground.length}`);
|
||||
if (metrics.badBackground.length) issues.push(`surface non-matrix background offenders ${metrics.badBackground.length}`);
|
||||
if (metrics.missingMatrix.length) issues.push(`surface missing dot-matrix offenders ${metrics.missingMatrix.length}`);
|
||||
if (metrics.badChipContrast.length) issues.push(`chip contrast offenders ${metrics.badChipContrast.length}`);
|
||||
return issues;
|
||||
}
|
||||
@@ -385,7 +406,7 @@ async function main() {
|
||||
const hero = result.metrics?.hero ? `${result.metrics.hero.height}px r=${result.metrics.hero.radius}` : 'n/a';
|
||||
console.log(`${status} ${result.viewport} ${result.route} title=${title} hero=${hero}${result.issues.length ? ` issues=${result.issues.join('; ')}` : ''}`);
|
||||
if (!result.passed && result.metrics) {
|
||||
for (const bucket of ['badRadius', 'badBackground', 'badChipContrast']) {
|
||||
for (const bucket of ['badRadius', 'badBackground', 'missingMatrix', 'badChipContrast']) {
|
||||
for (const offender of result.metrics[bucket] || []) {
|
||||
console.log(` ${bucket}: ${JSON.stringify(offender)}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user