@tangle-network/browser-agent-driver 0.12.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/brain/index.d.ts.map +1 -1
- package/dist/brain/index.js +8 -0
- package/dist/brain/index.js.map +1 -1
- package/dist/cli-design-audit.d.ts +2 -0
- package/dist/cli-design-audit.d.ts.map +1 -1
- package/dist/cli-design-audit.js +111 -303
- package/dist/cli-design-audit.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/design/audit/classify.d.ts +20 -0
- package/dist/design/audit/classify.d.ts.map +1 -0
- package/dist/design/audit/classify.js +148 -0
- package/dist/design/audit/classify.js.map +1 -0
- package/dist/design/audit/evaluate.d.ts +49 -0
- package/dist/design/audit/evaluate.d.ts.map +1 -0
- package/dist/design/audit/evaluate.js +353 -0
- package/dist/design/audit/evaluate.js.map +1 -0
- package/dist/design/audit/measure/a11y.d.ts +25 -0
- package/dist/design/audit/measure/a11y.d.ts.map +1 -0
- package/dist/design/audit/measure/a11y.js +144 -0
- package/dist/design/audit/measure/a11y.js.map +1 -0
- package/dist/design/audit/measure/contrast.d.ts +18 -0
- package/dist/design/audit/measure/contrast.d.ts.map +1 -0
- package/dist/design/audit/measure/contrast.js +201 -0
- package/dist/design/audit/measure/contrast.js.map +1 -0
- package/dist/design/audit/measure/index.d.ts +14 -0
- package/dist/design/audit/measure/index.d.ts.map +1 -0
- package/dist/design/audit/measure/index.js +38 -0
- package/dist/design/audit/measure/index.js.map +1 -0
- package/dist/design/audit/pipeline.d.ts +33 -0
- package/dist/design/audit/pipeline.d.ts.map +1 -0
- package/dist/design/audit/pipeline.js +109 -0
- package/dist/design/audit/pipeline.js.map +1 -0
- package/dist/design/audit/roi.d.ts +49 -0
- package/dist/design/audit/roi.d.ts.map +1 -0
- package/dist/design/audit/roi.js +157 -0
- package/dist/design/audit/roi.js.map +1 -0
- package/dist/design/audit/rubric/fragments/domain-ai.md +19 -0
- package/dist/design/audit/rubric/fragments/domain-crypto.md +24 -0
- package/dist/design/audit/rubric/fragments/domain-devtools.md +21 -0
- package/dist/design/audit/rubric/fragments/domain-fintech.md +19 -0
- package/dist/design/audit/rubric/fragments/maturity-prototype.md +36 -0
- package/dist/design/audit/rubric/fragments/type-blog.md +22 -0
- package/dist/design/audit/rubric/fragments/type-docs.md +23 -0
- package/dist/design/audit/rubric/fragments/type-ecommerce.md +23 -0
- package/dist/design/audit/rubric/fragments/type-marketing.md +22 -0
- package/dist/design/audit/rubric/fragments/type-saas-app.md +20 -0
- package/dist/design/audit/rubric/fragments/universal-calibration.md +16 -0
- package/dist/design/audit/rubric/fragments/universal-effort-anchor.md +46 -0
- package/dist/design/audit/rubric/fragments/universal-foundation.md +49 -0
- package/dist/design/audit/rubric/loader.d.ts +49 -0
- package/dist/design/audit/rubric/loader.d.ts.map +1 -0
- package/dist/design/audit/rubric/loader.js +216 -0
- package/dist/design/audit/rubric/loader.js.map +1 -0
- package/dist/design/audit/types.d.ts +166 -0
- package/dist/design/audit/types.d.ts.map +1 -0
- package/dist/design/audit/types.js +8 -0
- package/dist/design/audit/types.js.map +1 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluator — composes classification + rubric + measurements + LLM vision
|
|
3
|
+
* into structured findings.
|
|
4
|
+
*
|
|
5
|
+
* Architecture:
|
|
6
|
+
* 1. Classifier output drives which rubric fragments apply.
|
|
7
|
+
* 2. Deterministic measurements (contrast, axe) become "ground-truth" findings
|
|
8
|
+
* injected directly into the result. The LLM is not asked to invent these.
|
|
9
|
+
* 3. The LLM's job is purely subjective: visual quality, hierarchy, polish,
|
|
10
|
+
* design system coherence. It sees the screenshot, the composed rubric,
|
|
11
|
+
* and a summary of the deterministic findings as context.
|
|
12
|
+
*
|
|
13
|
+
* This split is the whole point of Gen 2: stop asking the LLM to estimate
|
|
14
|
+
* things that are computable.
|
|
15
|
+
*/
|
|
16
|
+
import { impactToSeverity } from './measure/index.js';
|
|
17
|
+
import { annotateRoi } from './roi.js';
|
|
18
|
+
/**
|
|
19
|
+
* Build the LLM prompt for visual evaluation. Includes:
|
|
20
|
+
* - The composed rubric (only fragments matching the classification)
|
|
21
|
+
* - A summary of the deterministic measurements (so the LLM knows what's
|
|
22
|
+
* already been counted and doesn't double-count)
|
|
23
|
+
* - Strict instructions: no estimating contrast, no inventing a11y findings
|
|
24
|
+
*/
|
|
25
|
+
const UNIVERSAL_DIMENSIONS = [
|
|
26
|
+
'layout',
|
|
27
|
+
'typography',
|
|
28
|
+
'color',
|
|
29
|
+
'spacing',
|
|
30
|
+
'components',
|
|
31
|
+
'interactions',
|
|
32
|
+
'accessibility',
|
|
33
|
+
'polish',
|
|
34
|
+
];
|
|
35
|
+
function buildEvalPrompt(input) {
|
|
36
|
+
const { classification, rubric, measurements } = input;
|
|
37
|
+
const measurementSummary = [
|
|
38
|
+
`CONTRAST (already measured by deterministic math, do not re-evaluate):`,
|
|
39
|
+
` - ${measurements.contrast.totalChecked} text elements checked`,
|
|
40
|
+
` - ${measurements.contrast.aaFailures.length} fail WCAG AA`,
|
|
41
|
+
` - ${measurements.contrast.aaaFailures.length} fail WCAG AAA`,
|
|
42
|
+
measurements.contrast.aaFailures.length > 0
|
|
43
|
+
? ` - Top failures: ${measurements.contrast.aaFailures
|
|
44
|
+
.slice(0, 3)
|
|
45
|
+
.map(f => `${f.color} on ${f.background} (${f.ratio}:1)`)
|
|
46
|
+
.join(', ')}`
|
|
47
|
+
: '',
|
|
48
|
+
'',
|
|
49
|
+
`ACCESSIBILITY (already measured by axe-core, do not re-evaluate):`,
|
|
50
|
+
` - axe ran: ${measurements.a11y.ran}`,
|
|
51
|
+
` - ${measurements.a11y.violations.length} WCAG violations found`,
|
|
52
|
+
measurements.a11y.violations.length > 0
|
|
53
|
+
? ` - Top issues: ${measurements.a11y.violations
|
|
54
|
+
.slice(0, 5)
|
|
55
|
+
.map(v => `${v.id} (${v.impact})`)
|
|
56
|
+
.join(', ')}`
|
|
57
|
+
: '',
|
|
58
|
+
]
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.join('\n');
|
|
61
|
+
// Compose the full dimension list (universal + custom from rubric fragments)
|
|
62
|
+
const allDimensions = [...UNIVERSAL_DIMENSIONS, ...rubric.dimensions];
|
|
63
|
+
const dimensionExample = allDimensions
|
|
64
|
+
.map(d => ` "${d}": ${Math.floor(Math.random() * 4) + 5}`)
|
|
65
|
+
.join(',\n');
|
|
66
|
+
return `You are a principal design engineer who has shipped design systems at Linear, Stripe, and Vercel. You review with the precision of a typographer and the ruthlessness of a design director.
|
|
67
|
+
|
|
68
|
+
You are evaluating a page that has been pre-classified and pre-measured. Your job is the SUBJECTIVE layer only: visual quality, hierarchy, polish, design system coherence. You must NOT invent contrast or accessibility findings — those have already been measured deterministically and will be merged with your output.
|
|
69
|
+
|
|
70
|
+
PAGE CLASSIFICATION:
|
|
71
|
+
- Type: ${classification.type}
|
|
72
|
+
- Domain: ${classification.domain}
|
|
73
|
+
- Framework: ${classification.framework ?? 'unknown'}
|
|
74
|
+
- Design system: ${classification.designSystem}
|
|
75
|
+
- Maturity: ${classification.maturity}
|
|
76
|
+
- Intent: ${classification.intent}
|
|
77
|
+
- Classifier confidence: ${classification.confidence}
|
|
78
|
+
|
|
79
|
+
DETERMINISTIC MEASUREMENTS:
|
|
80
|
+
${measurementSummary}
|
|
81
|
+
|
|
82
|
+
EVALUATION RUBRIC (composed from fragments matching this page):
|
|
83
|
+
|
|
84
|
+
${rubric.body}
|
|
85
|
+
|
|
86
|
+
YOUR JOB:
|
|
87
|
+
1. Score this page 1-10 against the rubric above. Use the calibration anchors strictly.
|
|
88
|
+
2. Produce findings ONLY for things you can SEE in the screenshot — visual hierarchy, typography choices, spacing rhythm, component coherence, polish details.
|
|
89
|
+
3. Do NOT produce contrast findings — they've been measured.
|
|
90
|
+
4. Do NOT produce accessibility findings — axe has been run.
|
|
91
|
+
5. Be specific. Reference exact elements, measured spacing, typography choices.
|
|
92
|
+
6. For each finding include a concrete CSS fix in the cssFix field.
|
|
93
|
+
7. For each finding ALSO include impact, effort, and blast — these drive the ROI ranking.
|
|
94
|
+
|
|
95
|
+
ROI FIELDS — score each finding on:
|
|
96
|
+
- impact (1-10): how much this hurts the user. 1 = nitpick, 10 = breaks the experience.
|
|
97
|
+
- effort (1-10): how hard the fix is. 1 = single CSS line, 5 = a few component edits, 10 = redesign.
|
|
98
|
+
- blast: scope of the fix's effect.
|
|
99
|
+
"page" = only this page benefits
|
|
100
|
+
"section" = a region of this page (hero, footer)
|
|
101
|
+
"component" = a shared component (Card, Button) — multiple pages benefit
|
|
102
|
+
"system" = a design token or global style — every page benefits
|
|
103
|
+
|
|
104
|
+
A high-blast / low-effort fix has massive ROI. Use this scale honestly — the user will fix the top-ROI items first.
|
|
105
|
+
|
|
106
|
+
RESPOND WITH ONLY a JSON object:
|
|
107
|
+
{
|
|
108
|
+
"score": 7,
|
|
109
|
+
"summary": "One-sentence assessment of the design system's coherence and the page's effectiveness for its classified intent",
|
|
110
|
+
"strengths": [
|
|
111
|
+
"Specific evidence-based strength",
|
|
112
|
+
"Another measured strength"
|
|
113
|
+
],
|
|
114
|
+
"findings": [
|
|
115
|
+
{
|
|
116
|
+
"category": "spacing",
|
|
117
|
+
"severity": "major",
|
|
118
|
+
"description": "Hero section has 64px top padding but only 16px bottom — inconsistent vertical rhythm breaks the 8px grid",
|
|
119
|
+
"location": "Hero section → features grid transition",
|
|
120
|
+
"suggestion": "Use 48px or 64px consistently for all major section transitions",
|
|
121
|
+
"cssSelector": "main > section:first-child",
|
|
122
|
+
"cssFix": "padding-bottom: 48px",
|
|
123
|
+
"impact": 6,
|
|
124
|
+
"effort": 1,
|
|
125
|
+
"blast": "page"
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"designSystemScore": {
|
|
129
|
+
${dimensionExample}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Categories: visual-bug, layout, alignment, spacing, typography, ux
|
|
134
|
+
(Do NOT use 'contrast' or 'accessibility' — those come from measurements.)
|
|
135
|
+
Severities: critical, major, minor
|
|
136
|
+
Score: 1-10. Most production apps score 5-7.
|
|
137
|
+
|
|
138
|
+
DIMENSIONS — score each of these on a 1-10 scale in designSystemScore:
|
|
139
|
+
${allDimensions.map(d => `- ${d}`).join('\n')}`;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Convert deterministic measurements into findings.
|
|
143
|
+
*
|
|
144
|
+
* Both contrast and a11y measurements are GROUPED before becoming findings:
|
|
145
|
+
* - Contrast: grouped by (color, background) pair → one finding per token
|
|
146
|
+
* mismatch, with affected element count. A site with 47 elements using
|
|
147
|
+
* the same failing gray gets ONE finding ("change --color-text-muted"),
|
|
148
|
+
* not 47 spammy entries.
|
|
149
|
+
* - axe: grouped by rule id → one finding per rule, with N affected nodes.
|
|
150
|
+
*
|
|
151
|
+
* Grouping has two big effects:
|
|
152
|
+
* 1. Top Fixes by ROI surfaces real systemic issues, not 5 copies of the same one.
|
|
153
|
+
* 2. blast scales with how many elements are affected — a contrast pair on
|
|
154
|
+
* 47 elements is `system`; a one-off is `page`.
|
|
155
|
+
*/
|
|
156
|
+
export function measurementsToFindings(measurements) {
|
|
157
|
+
const findings = [];
|
|
158
|
+
// ── Contrast: group by (normalizedColor|normalizedBackground) ──
|
|
159
|
+
const contrastGroups = new Map();
|
|
160
|
+
for (const f of measurements.contrast.aaFailures) {
|
|
161
|
+
const key = `${f.color}|${f.background}`;
|
|
162
|
+
const existing = contrastGroups.get(key);
|
|
163
|
+
if (existing) {
|
|
164
|
+
existing.selectors.push(f.selector);
|
|
165
|
+
// Track the worst (lowest) ratio in the group
|
|
166
|
+
if (f.ratio < existing.ratio)
|
|
167
|
+
existing.ratio = f.ratio;
|
|
168
|
+
if (f.ratio < f.required - 1.5)
|
|
169
|
+
existing.isCritical = true;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
contrastGroups.set(key, {
|
|
173
|
+
color: f.color,
|
|
174
|
+
background: f.background,
|
|
175
|
+
ratio: f.ratio,
|
|
176
|
+
required: f.required,
|
|
177
|
+
selectors: [f.selector],
|
|
178
|
+
sampleText: f.text,
|
|
179
|
+
isCritical: f.ratio < f.required - 1.5,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Convert each group to a single finding. Cap to top 10 groups by element count.
|
|
184
|
+
const sortedGroups = [...contrastGroups.values()].sort((a, b) => b.selectors.length - a.selectors.length);
|
|
185
|
+
for (const g of sortedGroups.slice(0, 10)) {
|
|
186
|
+
const count = g.selectors.length;
|
|
187
|
+
const target = g.required.toFixed(1);
|
|
188
|
+
// Blast scales with how many elements use this color pair
|
|
189
|
+
const blast = count >= 5 ? 'system' : count >= 2 ? 'component' : 'page';
|
|
190
|
+
findings.push({
|
|
191
|
+
category: 'contrast',
|
|
192
|
+
severity: g.isCritical ? 'critical' : 'major',
|
|
193
|
+
description: count > 1
|
|
194
|
+
? `Text color ${g.color} on background ${g.background} fails WCAG AA on ${count} elements (worst ratio ${g.ratio}:1, need ${target}:1)`
|
|
195
|
+
: `Text color ${g.color} on background ${g.background} has contrast ratio ${g.ratio}:1, fails WCAG AA (need ${target}:1)`,
|
|
196
|
+
location: count > 1
|
|
197
|
+
? `${count} elements (e.g. ${g.selectors[0]})`
|
|
198
|
+
: `${g.selectors[0]} — "${g.sampleText}"`,
|
|
199
|
+
suggestion: count > 1
|
|
200
|
+
? `Change the shared color token. ${count} elements use this pairing — fix once, all benefit. Increase contrast to at least ${target}:1.`
|
|
201
|
+
: `Increase contrast to at least ${target}:1. Darken the text color or lighten the background.`,
|
|
202
|
+
cssSelector: g.selectors[0],
|
|
203
|
+
cssFix: `/* ${count} element${count !== 1 ? 's' : ''} affected: ${g.ratio}:1 → need ${target}:1 */`,
|
|
204
|
+
impact: g.isCritical ? 9 : 7,
|
|
205
|
+
effort: 1,
|
|
206
|
+
blast,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// ── Accessibility: group by axe rule id ──
|
|
210
|
+
// axe already returns one violation per rule (with multiple nodes), so the
|
|
211
|
+
// grouping is mostly about reframing the description to surface the count.
|
|
212
|
+
for (const v of measurements.a11y.violations.slice(0, 15)) {
|
|
213
|
+
const nodeCount = v.nodes.length;
|
|
214
|
+
const firstNode = v.nodes[0];
|
|
215
|
+
const blast = nodeCount >= 5 ? 'system' : nodeCount >= 2 ? 'component' : 'page';
|
|
216
|
+
findings.push({
|
|
217
|
+
category: 'accessibility',
|
|
218
|
+
severity: impactToSeverity(v.impact),
|
|
219
|
+
description: nodeCount > 1
|
|
220
|
+
? `[axe: ${v.id}] ${v.description} (${nodeCount} affected elements)`
|
|
221
|
+
: `[axe: ${v.id}] ${v.description}`,
|
|
222
|
+
location: firstNode
|
|
223
|
+
? nodeCount > 1
|
|
224
|
+
? `${nodeCount} elements (e.g. ${firstNode.selector})`
|
|
225
|
+
: firstNode.selector
|
|
226
|
+
: 'page',
|
|
227
|
+
suggestion: nodeCount > 1
|
|
228
|
+
? `${firstNode?.failureSummary ?? 'Fix all affected elements.'} ${nodeCount} elements affected — likely a shared component bug.`
|
|
229
|
+
: firstNode?.failureSummary || `See ${v.helpUrl}`,
|
|
230
|
+
...(firstNode ? { cssSelector: firstNode.selector } : {}),
|
|
231
|
+
impact: v.impact === 'critical' ? 9 : v.impact === 'serious' ? 7 : v.impact === 'moderate' ? 5 : 3,
|
|
232
|
+
effort: 3,
|
|
233
|
+
blast,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return findings;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Run the full evaluation pipeline for one page.
|
|
240
|
+
*
|
|
241
|
+
* Returns a PageAuditResult with merged findings (deterministic + LLM visual).
|
|
242
|
+
*/
|
|
243
|
+
export async function evaluatePage(brain, input) {
|
|
244
|
+
// Run the LLM visual evaluation
|
|
245
|
+
const result = await brain.auditDesign(input.state, `Audit the visual design quality of this ${input.classification.type} page`, [], buildEvalPrompt(input));
|
|
246
|
+
// Parse summary/strengths/designSystemScore from raw LLM response
|
|
247
|
+
let summary = '';
|
|
248
|
+
let strengths = [];
|
|
249
|
+
let designSystemScore;
|
|
250
|
+
try {
|
|
251
|
+
let text = result.raw.trim();
|
|
252
|
+
if (text.startsWith('```')) {
|
|
253
|
+
text = text.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
|
|
254
|
+
}
|
|
255
|
+
const start = text.indexOf('{');
|
|
256
|
+
const end = text.lastIndexOf('}');
|
|
257
|
+
if (start >= 0 && end > start) {
|
|
258
|
+
const parsed = JSON.parse(text.slice(start, end + 1));
|
|
259
|
+
summary = typeof parsed.summary === 'string' ? parsed.summary : '';
|
|
260
|
+
strengths = Array.isArray(parsed.strengths) ? parsed.strengths : [];
|
|
261
|
+
if (parsed.designSystemScore && typeof parsed.designSystemScore === 'object') {
|
|
262
|
+
designSystemScore = {};
|
|
263
|
+
for (const [k, v] of Object.entries(parsed.designSystemScore)) {
|
|
264
|
+
if (typeof v === 'number')
|
|
265
|
+
designSystemScore[k] = v;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// fall through with defaults
|
|
272
|
+
}
|
|
273
|
+
// Filter LLM findings to only the categories we expect from the visual layer
|
|
274
|
+
// (the LLM should not be producing contrast/accessibility findings, but be defensive)
|
|
275
|
+
const visualCategories = new Set([
|
|
276
|
+
'visual-bug',
|
|
277
|
+
'layout',
|
|
278
|
+
'alignment',
|
|
279
|
+
'spacing',
|
|
280
|
+
'typography',
|
|
281
|
+
'ux',
|
|
282
|
+
]);
|
|
283
|
+
const visualFindings = result.findings.filter(f => visualCategories.has(f.category));
|
|
284
|
+
// Merge: deterministic measurements first (they're ground truth), then visual
|
|
285
|
+
const measurementFindings = measurementsToFindings(input.measurements);
|
|
286
|
+
const mergedFindings = [...measurementFindings, ...visualFindings];
|
|
287
|
+
// Annotate every finding with its computed ROI score so downstream sorting works.
|
|
288
|
+
// Visual findings have impact/effort/blast from the LLM; measurement findings
|
|
289
|
+
// get derived defaults below.
|
|
290
|
+
annotateRoi(mergedFindings);
|
|
291
|
+
// Override the accessibility dimension in the design system score with
|
|
292
|
+
// measurement-driven truth. The overall score still reflects visual quality
|
|
293
|
+
// (the LLM's job) — but the a11y dimension is no longer LLM-estimated.
|
|
294
|
+
if (designSystemScore) {
|
|
295
|
+
designSystemScore.accessibility = computeAccessibilityScore(input.measurements);
|
|
296
|
+
}
|
|
297
|
+
// Only hard-cap the overall score in catastrophic cases (broken contrast on
|
|
298
|
+
// most text). Otherwise trust the LLM's visual judgment and let the
|
|
299
|
+
// accessibility dimension carry the measurement story.
|
|
300
|
+
let finalScore = result.score;
|
|
301
|
+
const contrastFailRate = 1 - input.measurements.contrast.summary.aaPassRate;
|
|
302
|
+
const trueCriticalA11y = input.measurements.a11y.violations.filter(v => v.impact === 'critical').length;
|
|
303
|
+
if (contrastFailRate > 0.5 && trueCriticalA11y >= 3) {
|
|
304
|
+
finalScore = Math.min(finalScore, 6);
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
url: input.url,
|
|
308
|
+
score: finalScore,
|
|
309
|
+
summary,
|
|
310
|
+
strengths,
|
|
311
|
+
findings: mergedFindings,
|
|
312
|
+
classification: input.classification,
|
|
313
|
+
rubricFragments: input.rubric.fragments.map(f => f.id),
|
|
314
|
+
measurements: input.measurements,
|
|
315
|
+
designSystemScore: designSystemScore,
|
|
316
|
+
screenshotPath: input.screenshotPath,
|
|
317
|
+
tokensUsed: result.tokensUsed,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Translate raw measurement data into a 1-10 accessibility score.
|
|
322
|
+
*
|
|
323
|
+
* Anchors:
|
|
324
|
+
* - 10: 100% AA contrast pass + 0 axe violations
|
|
325
|
+
* - 8: 95%+ AA contrast pass + ≤2 minor/moderate violations
|
|
326
|
+
* - 6: 90%+ AA contrast pass + 0 critical violations
|
|
327
|
+
* - 4: 75%+ AA contrast pass OR 1 critical violation
|
|
328
|
+
* - 2: <75% AA contrast pass OR 3+ critical violations
|
|
329
|
+
*/
|
|
330
|
+
function computeAccessibilityScore(measurements) {
|
|
331
|
+
const aaPass = measurements.contrast.summary.aaPassRate;
|
|
332
|
+
const critical = measurements.a11y.violations.filter(v => v.impact === 'critical').length;
|
|
333
|
+
const serious = measurements.a11y.violations.filter(v => v.impact === 'serious').length;
|
|
334
|
+
const moderate = measurements.a11y.violations.filter(v => v.impact === 'moderate').length;
|
|
335
|
+
// Catastrophic
|
|
336
|
+
if (aaPass < 0.5 || critical >= 3)
|
|
337
|
+
return 2;
|
|
338
|
+
// Severe
|
|
339
|
+
if (aaPass < 0.75 || critical >= 1)
|
|
340
|
+
return 4;
|
|
341
|
+
// Significant issues
|
|
342
|
+
if (aaPass < 0.9 || serious >= 3)
|
|
343
|
+
return 6;
|
|
344
|
+
// Minor issues
|
|
345
|
+
if (aaPass < 0.95 || serious >= 1 || moderate >= 3)
|
|
346
|
+
return 7;
|
|
347
|
+
// Mostly clean
|
|
348
|
+
if (aaPass < 1 || moderate >= 1)
|
|
349
|
+
return 8;
|
|
350
|
+
// Pristine
|
|
351
|
+
return 10;
|
|
352
|
+
}
|
|
353
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../../src/design/audit/evaluate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* axe-core a11y measurement.
|
|
3
|
+
*
|
|
4
|
+
* Injects axe-core into the page and runs `axe.run()` to get ground-truth
|
|
5
|
+
* WCAG violations. Replaces the LLM's accessibility guesses with the
|
|
6
|
+
* industry-standard rule engine maintained by Deque.
|
|
7
|
+
*
|
|
8
|
+
* axe-core is bundled as a dependency; we read its source from node_modules
|
|
9
|
+
* and inject it via Playwright's `page.addScriptTag` so it works regardless
|
|
10
|
+
* of CSP.
|
|
11
|
+
*/
|
|
12
|
+
import type { Page } from 'playwright';
|
|
13
|
+
import type { A11yReport, A11yViolation } from '../types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Run axe-core against the current page state.
|
|
16
|
+
*
|
|
17
|
+
* Failures of this function are non-fatal — the audit continues without
|
|
18
|
+
* a11y findings if axe can't be injected (e.g., strict CSP).
|
|
19
|
+
*/
|
|
20
|
+
export declare function measureA11y(page: Page): Promise<A11yReport>;
|
|
21
|
+
/**
|
|
22
|
+
* Map axe impact level to our DesignFinding severity scale.
|
|
23
|
+
*/
|
|
24
|
+
export declare function impactToSeverity(impact: A11yViolation['impact']): 'critical' | 'major' | 'minor';
|
|
25
|
+
//# sourceMappingURL=a11y.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y.d.ts","sourceRoot":"","sources":["../../../../src/design/audit/measure/a11y.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AA6C5D;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CA6FjE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAWhG"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* axe-core a11y measurement.
|
|
3
|
+
*
|
|
4
|
+
* Injects axe-core into the page and runs `axe.run()` to get ground-truth
|
|
5
|
+
* WCAG violations. Replaces the LLM's accessibility guesses with the
|
|
6
|
+
* industry-standard rule engine maintained by Deque.
|
|
7
|
+
*
|
|
8
|
+
* axe-core is bundled as a dependency; we read its source from node_modules
|
|
9
|
+
* and inject it via Playwright's `page.addScriptTag` so it works regardless
|
|
10
|
+
* of CSP.
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
// Resolve axe-core's bundled JS once per process. Walk up from this file's
|
|
17
|
+
// dist location to find node_modules — works in both `dist/` and source layout.
|
|
18
|
+
let cachedAxeSource = null;
|
|
19
|
+
function getAxeSource() {
|
|
20
|
+
if (cachedAxeSource !== null)
|
|
21
|
+
return cachedAxeSource;
|
|
22
|
+
// Try multiple candidate paths because compilation may relocate this file.
|
|
23
|
+
const candidates = [];
|
|
24
|
+
let cursor = __dirname;
|
|
25
|
+
for (let i = 0; i < 6; i++) {
|
|
26
|
+
candidates.push(path.join(cursor, 'node_modules/axe-core/axe.min.js'));
|
|
27
|
+
cursor = path.dirname(cursor);
|
|
28
|
+
}
|
|
29
|
+
for (const c of candidates) {
|
|
30
|
+
if (fs.existsSync(c)) {
|
|
31
|
+
cachedAxeSource = fs.readFileSync(c, 'utf-8');
|
|
32
|
+
return cachedAxeSource;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
throw new Error('axe-core/axe.min.js not found in any parent node_modules');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Run axe-core against the current page state.
|
|
39
|
+
*
|
|
40
|
+
* Failures of this function are non-fatal — the audit continues without
|
|
41
|
+
* a11y findings if axe can't be injected (e.g., strict CSP).
|
|
42
|
+
*/
|
|
43
|
+
export async function measureA11y(page) {
|
|
44
|
+
let axeSource;
|
|
45
|
+
try {
|
|
46
|
+
axeSource = getAxeSource();
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
return {
|
|
50
|
+
ran: false,
|
|
51
|
+
error: err instanceof Error ? err.message : String(err),
|
|
52
|
+
violations: [],
|
|
53
|
+
passes: 0,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
// Inject axe-core. CSP-strict pages (Stripe, GitHub, etc) block addScriptTag,
|
|
58
|
+
// so we fall back to direct evaluate() which CSP allows because it runs in
|
|
59
|
+
// the puppeteer/playwright extension world, not the page context.
|
|
60
|
+
let injected = false;
|
|
61
|
+
try {
|
|
62
|
+
await page.addScriptTag({ content: axeSource });
|
|
63
|
+
injected = true;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// CSP blocked the script tag — try CDP-based bypass
|
|
67
|
+
try {
|
|
68
|
+
const session = await page.context().newCDPSession(page);
|
|
69
|
+
await session.send('Page.addScriptToEvaluateOnNewDocument', { source: axeSource });
|
|
70
|
+
// Reload to apply
|
|
71
|
+
await page.reload({ waitUntil: 'domcontentloaded', timeout: 15_000 });
|
|
72
|
+
injected = true;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// CDP failed too — last resort: inject via evaluate (works on most CSP configs)
|
|
76
|
+
await page.evaluate((src) => {
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
78
|
+
new Function(src)();
|
|
79
|
+
}, axeSource);
|
|
80
|
+
injected = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!injected) {
|
|
84
|
+
return {
|
|
85
|
+
ran: false,
|
|
86
|
+
error: 'all injection methods failed (CSP)',
|
|
87
|
+
violations: [],
|
|
88
|
+
passes: 0,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Run with WCAG 2.1 AA tags. Limit nodes per violation to keep payload manageable.
|
|
92
|
+
const raw = (await page.evaluate(async () => {
|
|
93
|
+
const w = window;
|
|
94
|
+
if (!w.axe) {
|
|
95
|
+
throw new Error('axe not loaded after injection');
|
|
96
|
+
}
|
|
97
|
+
return w.axe.run(document, {
|
|
98
|
+
runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'] },
|
|
99
|
+
resultTypes: ['violations', 'passes'],
|
|
100
|
+
});
|
|
101
|
+
}));
|
|
102
|
+
const violations = raw.violations.map(v => ({
|
|
103
|
+
id: v.id,
|
|
104
|
+
impact: v.impact ?? 'moderate',
|
|
105
|
+
description: v.description || v.help,
|
|
106
|
+
tags: v.tags,
|
|
107
|
+
helpUrl: v.helpUrl,
|
|
108
|
+
nodes: v.nodes.slice(0, 5).map(n => ({
|
|
109
|
+
selector: Array.isArray(n.target) ? n.target.join(' ') : String(n.target),
|
|
110
|
+
html: (n.html ?? '').slice(0, 200),
|
|
111
|
+
failureSummary: (n.failureSummary ?? '').slice(0, 300),
|
|
112
|
+
})),
|
|
113
|
+
}));
|
|
114
|
+
return {
|
|
115
|
+
ran: true,
|
|
116
|
+
violations,
|
|
117
|
+
passes: raw.passes.length,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
return {
|
|
122
|
+
ran: false,
|
|
123
|
+
error: err instanceof Error ? err.message : String(err),
|
|
124
|
+
violations: [],
|
|
125
|
+
passes: 0,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Map axe impact level to our DesignFinding severity scale.
|
|
131
|
+
*/
|
|
132
|
+
export function impactToSeverity(impact) {
|
|
133
|
+
switch (impact) {
|
|
134
|
+
case 'critical':
|
|
135
|
+
case 'serious':
|
|
136
|
+
return 'critical';
|
|
137
|
+
case 'moderate':
|
|
138
|
+
return 'major';
|
|
139
|
+
case 'minor':
|
|
140
|
+
default:
|
|
141
|
+
return 'minor';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=a11y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y.js","sourceRoot":"","sources":["../../../../src/design/audit/measure/a11y.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real WCAG 2.1 contrast measurement.
|
|
3
|
+
*
|
|
4
|
+
* Walks every visible text element in the page, computes its actual rendered
|
|
5
|
+
* text color and resolved background color (climbing the parent chain through
|
|
6
|
+
* transparent backgrounds), then calculates the WCAG relative luminance ratio.
|
|
7
|
+
*
|
|
8
|
+
* This is deterministic — pure math, no LLM. Replaces the LLM's "estimated
|
|
9
|
+
* contrast ratio" guesses with ground truth.
|
|
10
|
+
*/
|
|
11
|
+
import type { Page } from 'playwright';
|
|
12
|
+
import type { ContrastReport } from '../types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Run contrast measurement on a Playwright page.
|
|
15
|
+
* Returns ground-truth WCAG contrast failures.
|
|
16
|
+
*/
|
|
17
|
+
export declare function measureContrast(page: Page): Promise<ContrastReport>;
|
|
18
|
+
//# sourceMappingURL=contrast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contrast.d.ts","sourceRoot":"","sources":["../../../../src/design/audit/measure/contrast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,aAAa,CAAA;AAuLlE;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,cAAc,CAAC,CAsBzE"}
|