import { verdict } from './guard-style-analysis.mjs'; // Exact source sites, not a single browser PASS for all CSS / dynamic branches. export function styleVerification(guard, runtime, cssEvidence, linked) { const widths = [390, 768, 1280, 1440]; const dynamic = (guard.tokenEvidence ?? []).flatMap(e => (e.dynamic ?? []).map(d => ({ ...d, definitions: e.definitions ?? [], incomplete: e.incomplete }))); const sites = [...(guard.styleEvidence?.checks ?? []).filter(c => c.designIntentStatus !== 'PASS'), ...dynamic]; const checks = []; const supplied = runtime.cssEvidence?.checks; for (const site of sites) { const measurements = []; for (const raw of Array.isArray(supplied) ? supplied.filter(c => c?.siteId === site.id) : []) { const named = cssEvidence.checks.find(c => c.name === raw.name && c.width === raw.width && c.selector === raw.selector && c.property === raw.property && c.actual === raw.actual && c.expected === raw.expected); const browser = raw.browser; const meta = named && linked && raw.file === site.file && raw.ruleId === site.ruleId && typeof raw.state === 'string' && raw.state.trim() && typeof raw.component === 'string' && raw.component.trim() && browser && typeof browser.name === 'string' && browser.name.trim() && typeof browser.version === 'string' && browser.version.trim() && Number.isFinite(raw.height) && raw.height > 0 && (!site.selector || raw.selector === site.selector || raw.sourceSelector === site.selector && raw.matchesSourceSelector === true) && (!site.component || raw.component.toLowerCase().replaceAll('-', '') === site.component.toLowerCase().replaceAll('-', '')); if (!meta) { measurements.push({ ...raw, status: 'NOT VERIFIED', reason: 'Missing / mismatched site, source, rule, DOM, state, viewport or browser metadata' }); continue; } if (site.ruleId === 'TOKEN-002') { const expression = raw.actualExpression; const names = typeof expression === 'string' ? [...expression.matchAll(/(?:var\(\s*|\()(--hm-[\w-]+)\s*(?=[,)])/g)].map(m => m[1]) : []; const validTokens = names.length > 0 && Array.isArray(raw.tokens) && names.every(n => raw.tokens.includes(n)); const missing = names.filter(n => !site.definitions.includes(n)); const malformed = typeof expression === 'string' && /var\(\s*--hm-[\w-]+\s+[^\s,)]/.test(expression); const tokenStatus = malformed ? 'FAIL' : !validTokens || site.incomplete ? 'NOT VERIFIED' : missing.length ? 'FAIL' : 'PASS'; measurements.push({ ...raw, status: verdict([named.status, tokenStatus]), reason: missing.length ? `Observed undefined Token: ${missing.join(', ')}` : !validTokens ? 'Actual dynamic expression / token references were not supplied' : 'Observed declarations and computed value checked; unobserved branches remain unverified' }); } else { const sameProperty = raw.property.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`) === site.property; measurements.push({ ...raw, status: sameProperty ? named.status : 'NOT VERIFIED', reason: sameProperty ? 'Measured actual / expected equality at named DOM state' : 'Measurement property does not match the declaration' }); } } const covered = widths.every(w => measurements.some(m => m.width === w && m.status === 'PASS')); const runtimeStatus = measurements.some(m => m.status === 'FAIL') ? 'FAIL' : covered && measurements.every(m => m.status === 'PASS') ? 'PASS' : 'NOT VERIFIED'; const staticStatus = site.staticStatus ?? site.status; const designIntentStatus = site.designIntentStatus ?? 'PASS'; checks.push({ id: site.id, ruleId: site.ruleId, file: site.file, line: site.line, component: site.component ?? null, selector: site.selector ?? null, property: site.property ?? null, tokens: site.tokens ?? [], staticStatus, runtimeStatus, designIntentStatus, finalStatus: verdict([staticStatus, runtimeStatus, designIntentStatus]), measurements, reason: site.reason ?? 'Dynamic source cannot prove all branches; browser observations are separate evidence' }); } const failures = (guard.issues ?? []).filter(i => !i.code.startsWith('RENDER-') && !['REUSE-003', 'TOKEN-002'].includes(i.code)).map(i => i.severity === 'FAIL' ? 'FAIL' : 'NOT VERIFIED'); const staticStatus = verdict([...failures, ...checks.map(c => c.staticStatus)]); const runtimeStatus = sites.length ? verdict(checks.map(c => c.runtimeStatus)) : 'NOT VERIFIED'; const designIntentStatus = verdict([...checks.map(c => c.designIntentStatus), ...(guard.issues ?? []).filter(i => i.code === 'REUSE-003').map(() => 'NOT VERIFIED')]); return { staticStatus, runtimeStatus, designIntentStatus, finalStatus: verdict([staticStatus, runtimeStatus, designIntentStatus]), checks, coverage: 'Named DOM / properties / four widths / supplied states only; unobserved themes, states and external inputs remain NOT VERIFIED' }; }