@tangle-network/browser-agent-driver 0.12.0 → 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 +112 -304
- 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
package/dist/cli-design-audit.js
CHANGED
|
@@ -14,224 +14,8 @@ import { PlaywrightDriver } from './drivers/playwright.js';
|
|
|
14
14
|
import { resolveProviderApiKey, resolveProviderModelName } from './provider-defaults.js';
|
|
15
15
|
import { loadLocalEnvFiles } from './env-loader.js';
|
|
16
16
|
import { cliError } from './cli-ui.js';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
const PROFILE_RUBRICS = {
|
|
21
|
-
general: `
|
|
22
|
-
CALIBRATION — be ruthlessly honest:
|
|
23
|
-
- 9-10: Only world-class design (Linear, Stripe, Vercel). Exceptional typography, spacing, micro-interactions.
|
|
24
|
-
- 7-8: Professional and polished. Minor inconsistencies tolerated if the overall system is coherent.
|
|
25
|
-
- 5-6: Functional but clearly lacks design investment. Default component libraries with no customization.
|
|
26
|
-
- 3-4: Noticeably broken or amateurish. Inconsistent spacing, clashing colors, poor hierarchy.
|
|
27
|
-
- 1-2: Unusable or visually broken. Overlapping elements, unreadable text, broken layouts.
|
|
28
|
-
|
|
29
|
-
Most production apps score 5-7. Very few deserve 8+. Do NOT grade on a curve.`,
|
|
30
|
-
saas: `
|
|
31
|
-
SaaS APPLICATION AUDIT — evaluate as a paying customer would:
|
|
32
|
-
- Information density: is data presented efficiently without clutter?
|
|
33
|
-
- Navigation: can I find features in <3 clicks? Is the sidebar/nav intuitive?
|
|
34
|
-
- Empty states: what happens with no data? Are there helpful onboarding prompts?
|
|
35
|
-
- Loading states: are there skeleton screens or spinners, or does content pop in?
|
|
36
|
-
- Form design: inline validation? Clear labels? Logical tab order?
|
|
37
|
-
- Dashboard layout: is the most important data prominent? Card hierarchy clear?
|
|
38
|
-
- Error states: are errors actionable with clear recovery paths?
|
|
39
|
-
- Consistency: do buttons, inputs, modals follow the same patterns throughout?
|
|
40
|
-
|
|
41
|
-
CALIBRATION: Linear, Notion, Figma = 9. Generic admin templates = 5. Broken CRUD apps = 3.`,
|
|
42
|
-
defi: `
|
|
43
|
-
DeFi/CRYPTO APPLICATION AUDIT — evaluate as a trader managing real money:
|
|
44
|
-
- Trust signals: does the UI feel safe to connect a wallet to? Professional or sketchy?
|
|
45
|
-
- Token displays: are balances formatted correctly? Right decimal places? USD equivalents?
|
|
46
|
-
- Transaction clarity: is it clear what you're signing? Amount, fees, slippage shown?
|
|
47
|
-
- Loading states: RPC calls are slow — are there proper loading indicators for balances, quotes, gas estimates?
|
|
48
|
-
- Mobile responsiveness: most DeFi usage is mobile — does it work on small viewports?
|
|
49
|
-
- Dark mode: most DeFi apps use dark mode — is contrast sufficient? Are borders visible?
|
|
50
|
-
- Error handling: what happens when RPC fails? Wallet rejects? Insufficient balance?
|
|
51
|
-
- Swap UX: is token selection intuitive? Can you paste addresses? Is the price impact shown?
|
|
52
|
-
- Wallet connection: is the connect flow smooth? Are supported wallets clearly shown?
|
|
53
|
-
- Gas/fee transparency: are gas estimates shown before confirmation?
|
|
54
|
-
|
|
55
|
-
CALIBRATION: Uniswap v4, Aave v3 = 8. Average DEX = 5. Rug-pull-looking sites = 2.
|
|
56
|
-
Uniswap is good but not perfect — dense token lists, some spacing issues, swap review could be clearer.`,
|
|
57
|
-
marketing: `
|
|
58
|
-
MARKETING/LANDING PAGE AUDIT — evaluate as a potential customer deciding in 10 seconds:
|
|
59
|
-
- Hero clarity: in 5 seconds, can I tell what this product does and who it's for?
|
|
60
|
-
- Visual hierarchy: does the eye flow naturally from headline → subtext → CTA?
|
|
61
|
-
- CTA prominence: is the primary call-to-action obvious and compelling?
|
|
62
|
-
- Social proof: are testimonials, logos, or metrics shown convincingly?
|
|
63
|
-
- Typography: is the headline typography impactful? Body text readable?
|
|
64
|
-
- Imagery: are images/illustrations high quality and relevant, or stock photo generic?
|
|
65
|
-
- Whitespace: does the page breathe, or is it cramped?
|
|
66
|
-
- Mobile: does the hero and CTA work on mobile without scrolling?
|
|
67
|
-
- Performance perception: does it feel fast? Are images optimized? No layout shift?
|
|
68
|
-
- Footer: is navigation complete? Legal links present?
|
|
69
|
-
|
|
70
|
-
CALIBRATION: Stripe, Linear, Vercel = 9. Average startup landing page = 5. Template sites = 3.`,
|
|
71
|
-
vibecoded: `
|
|
72
|
-
VIBECODED / AI-GENERATED APP AUDIT — evaluate as a design-literate user who can smell defaults:
|
|
73
|
-
|
|
74
|
-
TEMPLATE DETECTION (the #1 sin of vibecoded apps):
|
|
75
|
-
- Is this clearly an unmodified shadcn/ui, MUI, Ant Design, or Chakra template? Score ceiling: 4 if yes.
|
|
76
|
-
- Default border-radius (6-8px shadcn, 4px MUI), default color palette (zinc/slate grays, blue-600 primary)?
|
|
77
|
-
- Default component spacing with no customization? Standard card shadows? Stock empty states?
|
|
78
|
-
- "Looks like every other AI-generated app" = automatic 3-4 score.
|
|
79
|
-
|
|
80
|
-
HIERARCHY & INFORMATION ARCHITECTURE:
|
|
81
|
-
- Is everything the same visual weight? (Common AI pattern: all cards same size, no primary/secondary distinction)
|
|
82
|
-
- Is there clear information hierarchy? Primary action vs secondary vs tertiary?
|
|
83
|
-
- Does the layout have purpose or is it "centered column of cards" (the AI default)?
|
|
84
|
-
- Navigation: is it a dumped list of features or thoughtfully organized?
|
|
85
|
-
|
|
86
|
-
DESIGN SYSTEM COHERENCE:
|
|
87
|
-
- Are there more than 3 distinct border-radius values? (Incoherent)
|
|
88
|
-
- Color palette: intentional and limited (4-6 colors) or random accumulation?
|
|
89
|
-
- Spacing: consistent rhythm on an 8px grid, or arbitrary per-component?
|
|
90
|
-
- Typography: deliberate scale with 3-4 sizes, or every component picking its own?
|
|
91
|
-
- Are interactive states (hover, focus, active, disabled) designed or browser-default?
|
|
92
|
-
|
|
93
|
-
CRAFT SIGNALS (what separates 7 from 9):
|
|
94
|
-
- Custom icons or generic Lucide/Heroicons dump?
|
|
95
|
-
- Micro-interactions: button press feedback, page transitions, loading skeletons?
|
|
96
|
-
- Empty states: designed illustrations or "No data found" text?
|
|
97
|
-
- Error states: helpful messages with recovery actions or raw error strings?
|
|
98
|
-
- Dark mode (if present): properly designed or just "invert colors"?
|
|
99
|
-
- Content-first: does real content drive the layout, or is it a container waiting for content?
|
|
100
|
-
|
|
101
|
-
AGENTIC APP SPECIFICS:
|
|
102
|
-
- Agent status indicators: is it clear what the agent is doing? Progress feedback?
|
|
103
|
-
- Streaming/loading: smooth token streaming or janky text replacement?
|
|
104
|
-
- Conversation UI: proper message bubbles with timestamps, or plain text dump?
|
|
105
|
-
- Tool call visualization: can the user see what tools the agent used?
|
|
106
|
-
- Error recovery: when the agent fails, is there a clear retry/edit path?
|
|
107
|
-
|
|
108
|
-
CALIBRATION:
|
|
109
|
-
- 9-10: Custom design system, thoughtful hierarchy, polished interactions (Linear, Cursor, v0.dev)
|
|
110
|
-
- 7-8: Modified template with intentional design decisions, consistent system
|
|
111
|
-
- 5-6: Lightly customized template, functional but generic (most AI-generated apps)
|
|
112
|
-
- 3-4: Unmodified component library, no design investment, "it works" energy
|
|
113
|
-
- 1-2: Broken layout, clashing styles, unusable
|
|
114
|
-
|
|
115
|
-
Most vibecoded apps score 3-5. The ceiling for unmodified templates is 4 regardless of functionality.`,
|
|
116
|
-
};
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// Upgraded system prompt — much more opinionated than the original
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
function buildAuditPrompt(profile) {
|
|
121
|
-
const rubric = PROFILE_RUBRICS[profile] || PROFILE_RUBRICS.general;
|
|
122
|
-
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. You have built and maintained production design systems used by millions.
|
|
123
|
-
|
|
124
|
-
Your job: perform an exhaustive visual design audit of this page. You must be specific enough that a developer could fix every issue from your report alone — reference exact elements, computed values, pixel measurements, and CSS properties.
|
|
125
|
-
|
|
126
|
-
EVALUATION FRAMEWORK (score each area 1-10, then weight into overall):
|
|
127
|
-
|
|
128
|
-
1. LAYOUT & GRID (weight: 15%)
|
|
129
|
-
- Is there a consistent grid system? What grid unit? (4px, 8px, etc.)
|
|
130
|
-
- Column alignment: do content blocks align to the same left/right edges?
|
|
131
|
-
- Content width: is max-width appropriate? (prose: 65-75ch, app: fluid with sidebar)
|
|
132
|
-
- Responsive: does the layout reflow intentionally or just shrink?
|
|
133
|
-
- Z-index layering: any stacking context issues? Overlapping elements?
|
|
134
|
-
- Check for: orphaned elements floating outside the grid, inconsistent container padding
|
|
135
|
-
|
|
136
|
-
2. TYPOGRAPHY SYSTEM (weight: 15%)
|
|
137
|
-
- Type scale: is there a clear hierarchy? Count distinct font-size values — more than 5-6 suggests no scale.
|
|
138
|
-
- Line height: body text should be 1.4-1.6, headings 1.1-1.3. Flag violations.
|
|
139
|
-
- Letter spacing: headings often need negative (-0.01 to -0.03em). Is it tuned?
|
|
140
|
-
- Font pairing: max 2 families (heading + body). Flag 3+.
|
|
141
|
-
- Text rendering: are long paragraphs wider than 75ch? That harms readability.
|
|
142
|
-
- Orphans/widows: any single-word last lines in headings?
|
|
143
|
-
- Font loading: is there FOUT/FOIT? Font-display strategy?
|
|
144
|
-
|
|
145
|
-
3. COLOR & CONTRAST (weight: 15%)
|
|
146
|
-
- WCAG AA compliance: normal text needs 4.5:1, large text (18px+/14px+ bold) needs 3:1. ESTIMATE ratios.
|
|
147
|
-
- Palette size: count distinct hues. More than 5-6 non-neutral hues = incoherent.
|
|
148
|
-
- Semantic color usage: is the primary color used consistently for primary actions?
|
|
149
|
-
- Background layering: do nested surfaces have clear elevation (bg-0, bg-1, bg-2)?
|
|
150
|
-
- Gray scale: are grays consistent? All blue-gray, or mixed warm/cool? Mixed = incoherent.
|
|
151
|
-
- Accent usage: are accent colors used sparingly or splashed everywhere?
|
|
152
|
-
|
|
153
|
-
4. SPACING & RHYTHM (weight: 15%)
|
|
154
|
-
- Grid adherence: what % of spacing values are multiples of the base unit?
|
|
155
|
-
- Vertical rhythm: are section gaps consistent? Measure gap between each major section.
|
|
156
|
-
- Component internal spacing: is padding consistent within similar components (all cards, all inputs)?
|
|
157
|
-
- Whitespace ratio: is there enough breathing room, or is everything cramped?
|
|
158
|
-
- Margin collapse issues: any unintended spacing from margin collapse?
|
|
159
|
-
|
|
160
|
-
5. COMPONENT CONSISTENCY (weight: 15%)
|
|
161
|
-
- Button variants: how many distinct button styles? Are they intentional variants or accidents?
|
|
162
|
-
- Input styling: are all form inputs styled consistently? Border, focus ring, label position?
|
|
163
|
-
- Card patterns: same border-radius, shadow, padding across all cards?
|
|
164
|
-
- Icon system: consistent size (16/20/24px), stroke width, and style?
|
|
165
|
-
- Border radius: count distinct values. More than 3 (e.g., 4px, 8px, full) = incoherent.
|
|
166
|
-
- Shadow system: consistent elevation scale or random drop shadows?
|
|
167
|
-
|
|
168
|
-
6. INTERACTION DESIGN (weight: 10%)
|
|
169
|
-
- Hover states: do interactive elements have visible hover feedback?
|
|
170
|
-
- Focus indicators: are there visible focus rings for keyboard navigation?
|
|
171
|
-
- Active/pressed states: button feedback on click?
|
|
172
|
-
- Transitions: are they present? Consistent duration (150-300ms)? Appropriate easing?
|
|
173
|
-
- Loading states: skeleton screens, spinners, or no loading feedback at all?
|
|
174
|
-
- Cursor changes: does cursor change to pointer on clickable elements?
|
|
175
|
-
|
|
176
|
-
7. ACCESSIBILITY (weight: 10%)
|
|
177
|
-
- Semantic HTML: are headings in order (h1 → h2 → h3)? Are buttons actually <button>?
|
|
178
|
-
- ARIA labels: do icon-only buttons have labels? Do images have alt text?
|
|
179
|
-
- Keyboard navigation: can you tell what's focused? Is tab order logical?
|
|
180
|
-
- Touch targets: are mobile tap targets at least 44x44px?
|
|
181
|
-
- Screen reader: is content structured so screen reader users get meaningful navigation?
|
|
182
|
-
|
|
183
|
-
8. VISUAL POLISH (weight: 5%)
|
|
184
|
-
- Pixel precision: any elements off by 1px? Misaligned text baselines?
|
|
185
|
-
- Image quality: are images sharp on retina displays (2x resolution)?
|
|
186
|
-
- Icon consistency: all from the same set, or a mix of styles/weights?
|
|
187
|
-
- Empty states: designed or raw "no data" text?
|
|
188
|
-
- Error states: styled or browser-default?
|
|
189
|
-
- Favicon and meta: present and professional?
|
|
190
|
-
|
|
191
|
-
${rubric}
|
|
192
|
-
|
|
193
|
-
SPECIFICITY REQUIREMENTS — your findings must be THIS specific:
|
|
194
|
-
- BAD: "Spacing is inconsistent" (vague, useless)
|
|
195
|
-
- GOOD: "Section gap between hero and features is 48px, but features-to-pricing is 24px and pricing-to-footer is 64px. Use consistent 48px or 64px vertical rhythm throughout."
|
|
196
|
-
- BAD: "Colors don't look right" (vague)
|
|
197
|
-
- GOOD: "Body text (#6b7280) on white background has ~4.6:1 contrast ratio (barely passes AA). The same gray on the light-gray card background (#f9fafb) drops to ~3.8:1 — fails AA for normal text. Darken body text to #4b5563 (7:1+)."
|
|
198
|
-
- BAD: "Typography needs work" (vague)
|
|
199
|
-
- GOOD: "6 distinct font sizes detected (12, 13, 14, 16, 20, 32px) with no clear scale. Consolidate to a 4-step scale: 14px body, 16px large, 24px h2, 36px h1. Current h2 at 20px lacks sufficient contrast with 16px body text."
|
|
200
|
-
|
|
201
|
-
For EACH finding, you MUST include a concrete CSS fix in the suggestion field. Not "improve spacing" but "gap: 48px" or "font-size: 14px; line-height: 1.5".
|
|
202
|
-
|
|
203
|
-
RESPOND WITH ONLY a JSON object:
|
|
204
|
-
{
|
|
205
|
-
"score": 6,
|
|
206
|
-
"summary": "One-sentence overall assessment with the key design system failure mode",
|
|
207
|
-
"strengths": ["Specific thing done well with evidence", "Another measured strength"],
|
|
208
|
-
"findings": [
|
|
209
|
-
{
|
|
210
|
-
"category": "spacing",
|
|
211
|
-
"severity": "major",
|
|
212
|
-
"description": "Hero section has 64px top padding but only 16px bottom padding before the feature grid, creating visual imbalance. The 4:1 ratio breaks vertical rhythm.",
|
|
213
|
-
"location": "Hero section → feature grid transition (main > section:nth-child(2))",
|
|
214
|
-
"suggestion": "padding-bottom: 48px on hero section. Standardize all section gaps to 48px or 64px.",
|
|
215
|
-
"cssSelector": "main > section:first-child",
|
|
216
|
-
"cssFix": "padding-bottom: 48px"
|
|
217
|
-
}
|
|
218
|
-
],
|
|
219
|
-
"designSystemScore": {
|
|
220
|
-
"layout": 7,
|
|
221
|
-
"typography": 5,
|
|
222
|
-
"color": 6,
|
|
223
|
-
"spacing": 4,
|
|
224
|
-
"components": 6,
|
|
225
|
-
"interactions": 3,
|
|
226
|
-
"accessibility": 5,
|
|
227
|
-
"polish": 4
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
Categories: visual-bug, layout, contrast, alignment, spacing, typography, accessibility, ux
|
|
232
|
-
Severities: critical (blocks usage or fails WCAG), major (looks unprofessional), minor (polish detail)
|
|
233
|
-
Score: 1-10 per calibration above. Most production apps score 5-7. Template apps score 3-5. Only world-class ships get 8+. Be honest — inflated scores help nobody.`;
|
|
234
|
-
}
|
|
17
|
+
import { auditOnePage } from './design/audit/pipeline.js';
|
|
18
|
+
import { detectSystemicFindings, topByRoi } from './design/audit/roi.js';
|
|
235
19
|
// ---------------------------------------------------------------------------
|
|
236
20
|
// Page discovery — find key pages by crawling links
|
|
237
21
|
// ---------------------------------------------------------------------------
|
|
@@ -282,7 +66,7 @@ async function discoverPages(page, startUrl, maxPages) {
|
|
|
282
66
|
// ---------------------------------------------------------------------------
|
|
283
67
|
// Report generation
|
|
284
68
|
// ---------------------------------------------------------------------------
|
|
285
|
-
function generateReport(results, profile) {
|
|
69
|
+
function generateReport(results, profile, topFixes = []) {
|
|
286
70
|
const lines = [];
|
|
287
71
|
const avgScore = results.length > 0
|
|
288
72
|
? results.reduce((sum, r) => sum + r.score, 0) / results.length
|
|
@@ -294,7 +78,18 @@ function generateReport(results, profile) {
|
|
|
294
78
|
const totalTokens = results.reduce((sum, r) => sum + (r.tokensUsed ?? 0), 0);
|
|
295
79
|
lines.push('# Design Audit Report');
|
|
296
80
|
lines.push('');
|
|
297
|
-
|
|
81
|
+
if (profile) {
|
|
82
|
+
lines.push(`**Profile:** ${profile}`);
|
|
83
|
+
}
|
|
84
|
+
// Surface per-page classification when present (Gen 2)
|
|
85
|
+
const classifications = results
|
|
86
|
+
.filter(r => r.classification)
|
|
87
|
+
.map(r => `${r.url}: ${r.classification.type}/${r.classification.domain} (${r.classification.maturity})`);
|
|
88
|
+
if (classifications.length > 0) {
|
|
89
|
+
lines.push(`**Auto-classified:**`);
|
|
90
|
+
for (const c of classifications)
|
|
91
|
+
lines.push(`- ${c}`);
|
|
92
|
+
}
|
|
298
93
|
lines.push(`**Pages audited:** ${results.length}`);
|
|
299
94
|
lines.push(`**Overall score:** ${avgScore.toFixed(1)}/10`);
|
|
300
95
|
lines.push(`**Findings:** ${allFindings.length} (${critical} critical, ${major} major, ${minor} minor)`);
|
|
@@ -305,6 +100,34 @@ function generateReport(results, profile) {
|
|
|
305
100
|
const scoreBar = '█'.repeat(Math.round(avgScore)) + '░'.repeat(10 - Math.round(avgScore));
|
|
306
101
|
lines.push(`\`${scoreBar}\` ${avgScore.toFixed(1)}/10`);
|
|
307
102
|
lines.push('');
|
|
103
|
+
// ── Top Fixes (by ROI) — the headline section users actually read first ──
|
|
104
|
+
if (topFixes.length > 0) {
|
|
105
|
+
lines.push('## Top Fixes (by ROI)');
|
|
106
|
+
lines.push('');
|
|
107
|
+
lines.push('Fix these first — sorted by impact × blast / effort:');
|
|
108
|
+
lines.push('');
|
|
109
|
+
for (let i = 0; i < topFixes.length; i++) {
|
|
110
|
+
const f = topFixes[i];
|
|
111
|
+
const tags = [];
|
|
112
|
+
if (f.pageCount && f.pageCount >= 2)
|
|
113
|
+
tags.push(`appears on ${f.pageCount} pages`);
|
|
114
|
+
if (f.blast === 'system')
|
|
115
|
+
tags.push('SYSTEMIC');
|
|
116
|
+
const tagStr = tags.length > 0 ? ` _(${tags.join(', ')})_` : '';
|
|
117
|
+
const roiStr = f.roi !== undefined ? f.roi.toFixed(1) : '—';
|
|
118
|
+
lines.push(`### ${i + 1}. [${f.severity}] ${f.description}${tagStr}`);
|
|
119
|
+
lines.push('');
|
|
120
|
+
lines.push(`- **ROI:** ${roiStr} · Impact ${f.impact ?? '—'} · Effort ${f.effort ?? '—'} · Blast ${f.blast ?? '—'}`);
|
|
121
|
+
lines.push(`- **Location:** ${f.location}`);
|
|
122
|
+
lines.push(`- **Fix:** ${f.suggestion}`);
|
|
123
|
+
if (f.cssSelector && f.cssFix) {
|
|
124
|
+
lines.push(`- **CSS:** \`${f.cssSelector} { ${f.cssFix} }\``);
|
|
125
|
+
}
|
|
126
|
+
lines.push('');
|
|
127
|
+
}
|
|
128
|
+
lines.push('---');
|
|
129
|
+
lines.push('');
|
|
130
|
+
}
|
|
308
131
|
// Per-page results
|
|
309
132
|
for (const result of results) {
|
|
310
133
|
lines.push(`## ${result.url}`);
|
|
@@ -326,9 +149,11 @@ function generateReport(results, profile) {
|
|
|
326
149
|
lines.push('**Design System Breakdown:**');
|
|
327
150
|
lines.push('');
|
|
328
151
|
const ds = result.designSystemScore;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
152
|
+
// Universal dimensions in fixed order, then any custom dimensions (alpha)
|
|
153
|
+
const universal = ['layout', 'typography', 'color', 'spacing', 'components', 'interactions', 'accessibility', 'polish'];
|
|
154
|
+
const custom = Object.keys(ds).filter(k => !universal.includes(k)).sort();
|
|
155
|
+
for (const key of [...universal, ...custom]) {
|
|
156
|
+
if (typeof ds[key] === 'number') {
|
|
332
157
|
const bar = '█'.repeat(Math.round(ds[key])) + '░'.repeat(10 - Math.round(ds[key]));
|
|
333
158
|
lines.push(`- ${key}: \`${bar}\` ${ds[key]}/10`);
|
|
334
159
|
}
|
|
@@ -362,19 +187,18 @@ export async function runDesignAudit(opts) {
|
|
|
362
187
|
await runTokenExtraction(opts);
|
|
363
188
|
return;
|
|
364
189
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
process.exit(1);
|
|
369
|
-
}
|
|
190
|
+
// Profile is optional — auto-classified by default. Override with --profile
|
|
191
|
+
// selects a single type fragment instead of letting the classifier decide.
|
|
192
|
+
const profile = opts.profile;
|
|
370
193
|
const maxPages = opts.pages ?? 5;
|
|
371
|
-
const provider = (opts.provider ?? '
|
|
194
|
+
const provider = (opts.provider ?? 'claude-code');
|
|
372
195
|
const modelName = resolveProviderModelName(provider, opts.model);
|
|
373
196
|
const apiKey = opts.apiKey ?? resolveProviderApiKey(provider);
|
|
374
197
|
const [vw, vh] = (opts.viewport ?? '1440x900').split('x').map(Number);
|
|
375
198
|
console.log('');
|
|
376
199
|
console.log(` ${chalk.bold('bad design-audit')}`);
|
|
377
|
-
|
|
200
|
+
const profileLabel = profile ?? chalk.dim('auto-classify');
|
|
201
|
+
console.log(` ${profileLabel} ${chalk.dim('·')} ${chalk.cyan(modelName)} ${chalk.dim('·')} ${vw}×${vh} ${chalk.dim('·')} up to ${maxPages} pages`);
|
|
378
202
|
console.log(` ${chalk.dim('→')} ${opts.url}`);
|
|
379
203
|
console.log('');
|
|
380
204
|
const browser = await chromium.launch({ headless: opts.headless ?? true });
|
|
@@ -402,20 +226,41 @@ export async function runDesignAudit(opts) {
|
|
|
402
226
|
llmTimeoutMs: 120_000, // design audits generate ~8k tokens of structured JSON — need 2min
|
|
403
227
|
});
|
|
404
228
|
const driver = new PlaywrightDriver(page);
|
|
405
|
-
// Audit each page
|
|
229
|
+
// Audit each page through the classify → measure → evaluate pipeline
|
|
406
230
|
const results = [];
|
|
407
231
|
for (let i = 0; i < pages.length; i++) {
|
|
408
232
|
const url = pages[i];
|
|
409
233
|
console.log(` ${chalk.dim(`[${i + 1}/${pages.length}]`)} ${url}`);
|
|
410
|
-
const
|
|
234
|
+
const gen2 = await auditOnePage({
|
|
235
|
+
brain,
|
|
236
|
+
driver,
|
|
237
|
+
page,
|
|
238
|
+
url,
|
|
239
|
+
profileOverride: profile,
|
|
240
|
+
screenshotDir,
|
|
241
|
+
userRubricsDir: opts.rubricsDir,
|
|
242
|
+
});
|
|
243
|
+
const result = gen2;
|
|
411
244
|
results.push(result);
|
|
412
245
|
const icon = result.score >= 8 ? chalk.green('✓') : result.score >= 5 ? chalk.yellow('~') : chalk.red('✗');
|
|
413
246
|
const scoreColor = result.score >= 8 ? chalk.green : result.score >= 5 ? chalk.yellow : chalk.red;
|
|
414
247
|
const findingCount = result.findings.length;
|
|
415
|
-
|
|
248
|
+
const classLabel = result.classification
|
|
249
|
+
? chalk.dim(` (${result.classification.type}/${result.classification.domain})`)
|
|
250
|
+
: '';
|
|
251
|
+
console.log(` ${icon} ${scoreColor(`${result.score}/10`)} ${chalk.dim('—')} ${findingCount} finding${findingCount !== 1 ? 's' : ''}${classLabel}`);
|
|
252
|
+
}
|
|
253
|
+
// Cross-page systemic detection + top-fixes ranking.
|
|
254
|
+
// Findings appearing on 2+ pages collapse into a single systemic finding.
|
|
255
|
+
// The deduped set drives the Top Fixes section at the top of the report.
|
|
256
|
+
let topFixes = [];
|
|
257
|
+
if (results.length > 0) {
|
|
258
|
+
const perPage = results.map(r => r.findings);
|
|
259
|
+
const deduped = detectSystemicFindings(perPage);
|
|
260
|
+
topFixes = topByRoi(deduped, 5);
|
|
416
261
|
}
|
|
417
262
|
// Generate report
|
|
418
|
-
const report = generateReport(results, profile);
|
|
263
|
+
const report = generateReport(results, profile, topFixes);
|
|
419
264
|
const reportPath = path.join(outputDir, 'report.md');
|
|
420
265
|
fs.writeFileSync(reportPath, report);
|
|
421
266
|
const allFindings = results.flatMap(r => r.findings);
|
|
@@ -430,6 +275,7 @@ export async function runDesignAudit(opts) {
|
|
|
430
275
|
profile,
|
|
431
276
|
url: opts.url,
|
|
432
277
|
pages: results,
|
|
278
|
+
topFixes,
|
|
433
279
|
summary: { avgScore, totalFindings: allFindings.length, critical, major, minor },
|
|
434
280
|
}, null, 2));
|
|
435
281
|
console.log(` ${chalk.dim('JSON →')} ${jsonPath}`);
|
|
@@ -457,8 +303,15 @@ export async function runDesignAudit(opts) {
|
|
|
457
303
|
for (let rep = 0; rep < 2; rep++) {
|
|
458
304
|
const repResults = [];
|
|
459
305
|
for (const url of pages) {
|
|
460
|
-
const
|
|
461
|
-
|
|
306
|
+
const gen2 = await auditOnePage({
|
|
307
|
+
brain,
|
|
308
|
+
driver,
|
|
309
|
+
page,
|
|
310
|
+
url,
|
|
311
|
+
profileOverride: profile,
|
|
312
|
+
userRubricsDir: opts.rubricsDir,
|
|
313
|
+
});
|
|
314
|
+
repResults.push(gen2);
|
|
462
315
|
}
|
|
463
316
|
const repAvg = repResults.reduce((s, r) => s + r.score, 0) / repResults.length;
|
|
464
317
|
scores.push(repAvg);
|
|
@@ -482,14 +335,19 @@ export async function runDesignAudit(opts) {
|
|
|
482
335
|
// --evolve=claude-code|codex|opencode|<custom> → agent dispatch
|
|
483
336
|
const evolveMode = opts.evolve === true || opts.evolve === 'true' || opts.evolve === 'css' ? 'css' : opts.evolve;
|
|
484
337
|
let evolveResult;
|
|
338
|
+
// Evolve loops still use the legacy single-profile re-audit. When Gen 2 auto-classified,
|
|
339
|
+
// synthesize a profile name from the first page's classification (or fall back to 'general').
|
|
340
|
+
const evolveProfile = profile
|
|
341
|
+
?? results[0]?.classification?.type
|
|
342
|
+
?? 'general';
|
|
485
343
|
if (evolveMode !== 'css') {
|
|
486
344
|
// Agent-dispatched evolve — a coding agent edits the actual source code
|
|
487
345
|
const projectDir = opts.projectDir ?? process.cwd();
|
|
488
|
-
evolveResult = await runAgentEvolveLoop(brain, driver, page, pages,
|
|
346
|
+
evolveResult = await runAgentEvolveLoop(brain, driver, page, pages, evolveProfile, results, outputDir, opts.evolveRounds ?? 3, evolveMode, projectDir, opts.debug);
|
|
489
347
|
}
|
|
490
348
|
else {
|
|
491
349
|
// CSS-injection evolve — ephemeral fixes injected into the browser page
|
|
492
|
-
evolveResult = await runEvolveLoop(brain, driver, page, pages,
|
|
350
|
+
evolveResult = await runEvolveLoop(brain, driver, page, pages, evolveProfile, results, outputDir, opts.evolveRounds ?? 3);
|
|
493
351
|
}
|
|
494
352
|
// Write evolve report
|
|
495
353
|
const evolvePath = path.join(outputDir, 'evolve-report.md');
|
|
@@ -598,7 +456,14 @@ async function runEvolveLoop(brain, driver, page, pages, profile, initialResults
|
|
|
598
456
|
// Take screenshot of fixed state
|
|
599
457
|
const screenshotDir = path.join(outputDir, `screenshots-round-${round}`);
|
|
600
458
|
fs.mkdirSync(screenshotDir, { recursive: true });
|
|
601
|
-
const result = await
|
|
459
|
+
const result = (await auditOnePage({
|
|
460
|
+
brain,
|
|
461
|
+
driver,
|
|
462
|
+
page,
|
|
463
|
+
url,
|
|
464
|
+
profileOverride: profile,
|
|
465
|
+
screenshotDir,
|
|
466
|
+
}));
|
|
602
467
|
roundResults.push(result);
|
|
603
468
|
}
|
|
604
469
|
catch {
|
|
@@ -847,7 +712,14 @@ async function runAgentEvolveLoop(brain, driver, page, pages, profile, initialRe
|
|
|
847
712
|
const roundScreenshotDir = path.join(outputDir, `screenshots-round-${round}`);
|
|
848
713
|
fs.mkdirSync(roundScreenshotDir, { recursive: true });
|
|
849
714
|
for (const url of pages) {
|
|
850
|
-
const result = await
|
|
715
|
+
const result = (await auditOnePage({
|
|
716
|
+
brain,
|
|
717
|
+
driver,
|
|
718
|
+
page,
|
|
719
|
+
url,
|
|
720
|
+
profileOverride: profile,
|
|
721
|
+
screenshotDir: roundScreenshotDir,
|
|
722
|
+
}));
|
|
851
723
|
roundResults.push(result);
|
|
852
724
|
}
|
|
853
725
|
const roundAvg = roundResults.reduce((s, r) => s + r.score, 0) / roundResults.length;
|
|
@@ -892,70 +764,6 @@ async function runAgentEvolveLoop(brain, driver, page, pages, profile, initialRe
|
|
|
892
764
|
cssOverride: '', // no CSS override in agent mode — agent edited source directly
|
|
893
765
|
};
|
|
894
766
|
}
|
|
895
|
-
// Direct page audit using brain.generate with custom prompt
|
|
896
|
-
async function auditSinglePage(brain, driver, page, url, profile, screenshotDir) {
|
|
897
|
-
try {
|
|
898
|
-
await page.goto(url, { waitUntil: 'networkidle', timeout: 20_000 }).catch(() => page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15_000 }));
|
|
899
|
-
await page.waitForTimeout(2000);
|
|
900
|
-
// Dismiss cookie banners
|
|
901
|
-
for (const sel of ['button:has-text("Accept all")', 'button:has-text("Accept")', 'button:has-text("Reject all")', 'button:has-text("Got it")', 'button:has-text("Close")']) {
|
|
902
|
-
const btn = page.locator(sel).first();
|
|
903
|
-
if (await btn.isVisible({ timeout: 500 }).catch(() => false)) {
|
|
904
|
-
await btn.click({ timeout: 2000 }).catch(() => null);
|
|
905
|
-
await page.waitForTimeout(500);
|
|
906
|
-
break;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
const state = await driver.observe();
|
|
910
|
-
// Save screenshot
|
|
911
|
-
let screenshotPath;
|
|
912
|
-
if (screenshotDir) {
|
|
913
|
-
const slug = new URL(url).pathname.replace(/\//g, '_').replace(/^_/, '') || 'index';
|
|
914
|
-
screenshotPath = path.join(screenshotDir, `${slug}.png`);
|
|
915
|
-
await page.screenshot({ path: screenshotPath, fullPage: false });
|
|
916
|
-
}
|
|
917
|
-
const result = await brain.auditDesign(state, `Audit the design quality of this page: ${url}`, [], buildAuditPrompt(profile));
|
|
918
|
-
let summary = '';
|
|
919
|
-
let strengths = [];
|
|
920
|
-
let designSystemScore;
|
|
921
|
-
try {
|
|
922
|
-
let text = result.raw.trim();
|
|
923
|
-
if (text.startsWith('```'))
|
|
924
|
-
text = text.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
|
|
925
|
-
const parsed = JSON.parse(text);
|
|
926
|
-
summary = parsed.summary || '';
|
|
927
|
-
strengths = Array.isArray(parsed.strengths) ? parsed.strengths : [];
|
|
928
|
-
if (parsed.designSystemScore && typeof parsed.designSystemScore === 'object') {
|
|
929
|
-
designSystemScore = {};
|
|
930
|
-
for (const [k, v] of Object.entries(parsed.designSystemScore)) {
|
|
931
|
-
if (typeof v === 'number')
|
|
932
|
-
designSystemScore[k] = v;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
catch { /* use defaults */ }
|
|
937
|
-
return {
|
|
938
|
-
url,
|
|
939
|
-
score: result.score,
|
|
940
|
-
summary,
|
|
941
|
-
strengths,
|
|
942
|
-
findings: result.findings,
|
|
943
|
-
screenshotPath,
|
|
944
|
-
tokensUsed: result.tokensUsed,
|
|
945
|
-
designSystemScore,
|
|
946
|
-
};
|
|
947
|
-
}
|
|
948
|
-
catch (err) {
|
|
949
|
-
return {
|
|
950
|
-
url,
|
|
951
|
-
score: 0,
|
|
952
|
-
summary: 'Failed to audit',
|
|
953
|
-
strengths: [],
|
|
954
|
-
findings: [],
|
|
955
|
-
error: err instanceof Error ? err.message : String(err),
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
767
|
// ---------------------------------------------------------------------------
|
|
960
768
|
// Design Token Extraction — pure DOM, no LLM calls
|
|
961
769
|
// ---------------------------------------------------------------------------
|