@ticktockbent/charlotte 0.1.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.
Files changed (136) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +254 -0
  4. package/dist/browser/browser-manager.d.ts +14 -0
  5. package/dist/browser/browser-manager.d.ts.map +1 -0
  6. package/dist/browser/browser-manager.js +72 -0
  7. package/dist/browser/browser-manager.js.map +1 -0
  8. package/dist/browser/cdp-session.d.ts +7 -0
  9. package/dist/browser/cdp-session.d.ts.map +1 -0
  10. package/dist/browser/cdp-session.js +35 -0
  11. package/dist/browser/cdp-session.js.map +1 -0
  12. package/dist/browser/page-manager.d.ts +30 -0
  13. package/dist/browser/page-manager.d.ts.map +1 -0
  14. package/dist/browser/page-manager.js +123 -0
  15. package/dist/browser/page-manager.js.map +1 -0
  16. package/dist/dev/auditor.d.ts +39 -0
  17. package/dist/dev/auditor.d.ts.map +1 -0
  18. package/dist/dev/auditor.js +474 -0
  19. package/dist/dev/auditor.js.map +1 -0
  20. package/dist/dev/dev-mode-state.d.ts +24 -0
  21. package/dist/dev/dev-mode-state.d.ts.map +1 -0
  22. package/dist/dev/dev-mode-state.js +93 -0
  23. package/dist/dev/dev-mode-state.js.map +1 -0
  24. package/dist/dev/file-watcher.d.ts +20 -0
  25. package/dist/dev/file-watcher.d.ts.map +1 -0
  26. package/dist/dev/file-watcher.js +78 -0
  27. package/dist/dev/file-watcher.js.map +1 -0
  28. package/dist/dev/static-server.d.ts +18 -0
  29. package/dist/dev/static-server.d.ts.map +1 -0
  30. package/dist/dev/static-server.js +73 -0
  31. package/dist/dev/static-server.js.map +1 -0
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +60 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/renderer/accessibility-extractor.d.ts +19 -0
  37. package/dist/renderer/accessibility-extractor.d.ts.map +1 -0
  38. package/dist/renderer/accessibility-extractor.js +138 -0
  39. package/dist/renderer/accessibility-extractor.js.map +1 -0
  40. package/dist/renderer/content-extractor.d.ts +6 -0
  41. package/dist/renderer/content-extractor.d.ts.map +1 -0
  42. package/dist/renderer/content-extractor.js +150 -0
  43. package/dist/renderer/content-extractor.js.map +1 -0
  44. package/dist/renderer/dom-path.d.ts +4 -0
  45. package/dist/renderer/dom-path.d.ts.map +1 -0
  46. package/dist/renderer/dom-path.js +34 -0
  47. package/dist/renderer/dom-path.js.map +1 -0
  48. package/dist/renderer/element-id-generator.d.ts +19 -0
  49. package/dist/renderer/element-id-generator.d.ts.map +1 -0
  50. package/dist/renderer/element-id-generator.js +73 -0
  51. package/dist/renderer/element-id-generator.js.map +1 -0
  52. package/dist/renderer/interactive-extractor.d.ts +13 -0
  53. package/dist/renderer/interactive-extractor.d.ts.map +1 -0
  54. package/dist/renderer/interactive-extractor.js +161 -0
  55. package/dist/renderer/interactive-extractor.js.map +1 -0
  56. package/dist/renderer/layout-extractor.d.ts +8 -0
  57. package/dist/renderer/layout-extractor.d.ts.map +1 -0
  58. package/dist/renderer/layout-extractor.js +48 -0
  59. package/dist/renderer/layout-extractor.js.map +1 -0
  60. package/dist/renderer/renderer-pipeline.d.ts +26 -0
  61. package/dist/renderer/renderer-pipeline.d.ts.map +1 -0
  62. package/dist/renderer/renderer-pipeline.js +163 -0
  63. package/dist/renderer/renderer-pipeline.js.map +1 -0
  64. package/dist/server.d.ts +19 -0
  65. package/dist/server.d.ts.map +1 -0
  66. package/dist/server.js +39 -0
  67. package/dist/server.js.map +1 -0
  68. package/dist/state/differ.d.ts +9 -0
  69. package/dist/state/differ.d.ts.map +1 -0
  70. package/dist/state/differ.js +295 -0
  71. package/dist/state/differ.js.map +1 -0
  72. package/dist/state/snapshot-store.d.ts +52 -0
  73. package/dist/state/snapshot-store.d.ts.map +1 -0
  74. package/dist/state/snapshot-store.js +98 -0
  75. package/dist/state/snapshot-store.js.map +1 -0
  76. package/dist/tools/dev-mode.d.ts +4 -0
  77. package/dist/tools/dev-mode.d.ts.map +1 -0
  78. package/dist/tools/dev-mode.js +160 -0
  79. package/dist/tools/dev-mode.js.map +1 -0
  80. package/dist/tools/evaluate.d.ts +10 -0
  81. package/dist/tools/evaluate.d.ts.map +1 -0
  82. package/dist/tools/evaluate.js +109 -0
  83. package/dist/tools/evaluate.js.map +1 -0
  84. package/dist/tools/interaction.d.ts +4 -0
  85. package/dist/tools/interaction.d.ts.map +1 -0
  86. package/dist/tools/interaction.js +680 -0
  87. package/dist/tools/interaction.js.map +1 -0
  88. package/dist/tools/navigation.d.ts +4 -0
  89. package/dist/tools/navigation.d.ts.map +1 -0
  90. package/dist/tools/navigation.js +136 -0
  91. package/dist/tools/navigation.js.map +1 -0
  92. package/dist/tools/observation.d.ts +4 -0
  93. package/dist/tools/observation.d.ts.map +1 -0
  94. package/dist/tools/observation.js +278 -0
  95. package/dist/tools/observation.js.map +1 -0
  96. package/dist/tools/session.d.ts +4 -0
  97. package/dist/tools/session.d.ts.map +1 -0
  98. package/dist/tools/session.js +372 -0
  99. package/dist/tools/session.js.map +1 -0
  100. package/dist/tools/tool-helpers.d.ts +89 -0
  101. package/dist/tools/tool-helpers.d.ts.map +1 -0
  102. package/dist/tools/tool-helpers.js +127 -0
  103. package/dist/tools/tool-helpers.js.map +1 -0
  104. package/dist/types/config.d.ts +7 -0
  105. package/dist/types/config.d.ts.map +1 -0
  106. package/dist/types/config.js +7 -0
  107. package/dist/types/config.js.map +1 -0
  108. package/dist/types/element-id.d.ts +8 -0
  109. package/dist/types/element-id.d.ts.map +1 -0
  110. package/dist/types/element-id.js +19 -0
  111. package/dist/types/element-id.js.map +1 -0
  112. package/dist/types/errors.d.ts +22 -0
  113. package/dist/types/errors.d.ts.map +1 -0
  114. package/dist/types/errors.js +30 -0
  115. package/dist/types/errors.js.map +1 -0
  116. package/dist/types/page-representation.d.ts +84 -0
  117. package/dist/types/page-representation.d.ts.map +1 -0
  118. package/dist/types/page-representation.js +2 -0
  119. package/dist/types/page-representation.js.map +1 -0
  120. package/dist/types/snapshot.d.ts +22 -0
  121. package/dist/types/snapshot.d.ts.map +1 -0
  122. package/dist/types/snapshot.js +2 -0
  123. package/dist/types/snapshot.js.map +1 -0
  124. package/dist/utils/hash.d.ts +2 -0
  125. package/dist/utils/hash.d.ts.map +1 -0
  126. package/dist/utils/hash.js +6 -0
  127. package/dist/utils/hash.js.map +1 -0
  128. package/dist/utils/logger.d.ts +9 -0
  129. package/dist/utils/logger.d.ts.map +1 -0
  130. package/dist/utils/logger.js +31 -0
  131. package/dist/utils/logger.js.map +1 -0
  132. package/dist/utils/wait.d.ts +21 -0
  133. package/dist/utils/wait.d.ts.map +1 -0
  134. package/dist/utils/wait.js +55 -0
  135. package/dist/utils/wait.js.map +1 -0
  136. package/package.json +67 -0
@@ -0,0 +1,474 @@
1
+ import { logger } from "../utils/logger.js";
2
+ const ALL_CATEGORIES = [
3
+ "a11y",
4
+ "performance",
5
+ "seo",
6
+ "contrast",
7
+ "links",
8
+ ];
9
+ /**
10
+ * Compute relative luminance of a color per WCAG 2.1.
11
+ * Input: r, g, b in 0-255 range.
12
+ */
13
+ export function relativeLuminance(r, g, b) {
14
+ const [rs, gs, bs] = [r / 255, g / 255, b / 255].map((channel) => channel <= 0.03928
15
+ ? channel / 12.92
16
+ : Math.pow((channel + 0.055) / 1.055, 2.4));
17
+ return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
18
+ }
19
+ /**
20
+ * Compute WCAG 2.1 contrast ratio between two colors.
21
+ * Returns a ratio >= 1.
22
+ */
23
+ export function contrastRatio(foregroundRgb, backgroundRgb) {
24
+ const luminanceForeground = relativeLuminance(...foregroundRgb);
25
+ const luminanceBackground = relativeLuminance(...backgroundRgb);
26
+ const lighter = Math.max(luminanceForeground, luminanceBackground);
27
+ const darker = Math.min(luminanceForeground, luminanceBackground);
28
+ return (lighter + 0.05) / (darker + 0.05);
29
+ }
30
+ /**
31
+ * Parse a CSS color string like "rgb(255, 0, 0)" or "rgba(255, 0, 0, 1)" to [r, g, b].
32
+ * Returns null if unparseable.
33
+ */
34
+ export function parseRgbColor(colorString) {
35
+ const rgbMatch = colorString.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
36
+ if (rgbMatch) {
37
+ return [
38
+ parseInt(rgbMatch[1], 10),
39
+ parseInt(rgbMatch[2], 10),
40
+ parseInt(rgbMatch[3], 10),
41
+ ];
42
+ }
43
+ return null;
44
+ }
45
+ function buildSummary(findings) {
46
+ if (findings.length === 0) {
47
+ return "No issues found.";
48
+ }
49
+ const errorCount = findings.filter((f) => f.severity === "error").length;
50
+ const warningCount = findings.filter((f) => f.severity === "warning").length;
51
+ const infoCount = findings.filter((f) => f.severity === "info").length;
52
+ const parts = [];
53
+ if (errorCount > 0)
54
+ parts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
55
+ if (warningCount > 0)
56
+ parts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
57
+ if (infoCount > 0)
58
+ parts.push(`${infoCount} info`);
59
+ return `${findings.length} finding${findings.length > 1 ? "s" : ""}: ${parts.join(", ")}`;
60
+ }
61
+ export class Auditor {
62
+ async audit(page, session, categories) {
63
+ const categoriesToCheck = categories && categories.length > 0
64
+ ? categories
65
+ : ALL_CATEGORIES;
66
+ const allFindings = [];
67
+ for (const category of categoriesToCheck) {
68
+ try {
69
+ const categoryFindings = await this.runCategoryAudit(category, page, session);
70
+ allFindings.push(...categoryFindings);
71
+ }
72
+ catch (error) {
73
+ logger.warn(`Audit category '${category}' failed`, error);
74
+ allFindings.push({
75
+ category,
76
+ severity: "warning",
77
+ message: `Audit check failed: ${error instanceof Error ? error.message : String(error)}`,
78
+ recommendation: "Check console for details and retry.",
79
+ });
80
+ }
81
+ }
82
+ return {
83
+ categories_checked: categoriesToCheck,
84
+ findings: allFindings,
85
+ summary: buildSummary(allFindings),
86
+ };
87
+ }
88
+ async runCategoryAudit(category, page, session) {
89
+ switch (category) {
90
+ case "a11y":
91
+ return this.auditAccessibility(session);
92
+ case "performance":
93
+ return this.auditPerformance(session);
94
+ case "seo":
95
+ return this.auditSeo(page);
96
+ case "contrast":
97
+ return this.auditContrast(page, session);
98
+ case "links":
99
+ return this.auditLinks(page);
100
+ }
101
+ }
102
+ async auditAccessibility(session) {
103
+ const findings = [];
104
+ const { nodes } = await session.send("Accessibility.getFullAXTree");
105
+ const headingLevels = [];
106
+ for (const node of nodes) {
107
+ const role = node.role?.value;
108
+ const name = node.name?.value;
109
+ const nameIsEmpty = !name || name.trim() === "";
110
+ // Images without alt text (AX role is "image", not "img")
111
+ if ((role === "image" || role === "img") && nameIsEmpty) {
112
+ findings.push({
113
+ category: "a11y",
114
+ severity: "error",
115
+ message: "Image has no accessible name (missing alt text).",
116
+ element: `backendNodeId:${node.backendDOMNodeId}`,
117
+ recommendation: "Add an alt attribute describing the image, or alt=\"\" for decorative images.",
118
+ });
119
+ }
120
+ // Buttons without labels
121
+ if (role === "button" && nameIsEmpty) {
122
+ findings.push({
123
+ category: "a11y",
124
+ severity: "error",
125
+ message: "Button has no accessible name.",
126
+ element: `backendNodeId:${node.backendDOMNodeId}`,
127
+ recommendation: "Add text content, aria-label, or aria-labelledby to the button.",
128
+ });
129
+ }
130
+ // Links without labels
131
+ if (role === "link" && nameIsEmpty) {
132
+ findings.push({
133
+ category: "a11y",
134
+ severity: "error",
135
+ message: "Link has no accessible name.",
136
+ element: `backendNodeId:${node.backendDOMNodeId}`,
137
+ recommendation: "Add text content, aria-label, or aria-labelledby to the link.",
138
+ });
139
+ }
140
+ // Form inputs without labels
141
+ if ((role === "textbox" ||
142
+ role === "combobox" ||
143
+ role === "checkbox" ||
144
+ role === "radio" ||
145
+ role === "spinbutton" ||
146
+ role === "slider") &&
147
+ nameIsEmpty) {
148
+ findings.push({
149
+ category: "a11y",
150
+ severity: "error",
151
+ message: `Form input (${role}) has no accessible name.`,
152
+ element: `backendNodeId:${node.backendDOMNodeId}`,
153
+ recommendation: "Add a <label> element, aria-label, or aria-labelledby.",
154
+ });
155
+ }
156
+ // Track heading levels for hierarchy check
157
+ if (role === "heading") {
158
+ const level = node.properties?.find((p) => p.name === "level")?.value?.value;
159
+ if (typeof level === "number") {
160
+ headingLevels.push(level);
161
+ }
162
+ }
163
+ }
164
+ // Check heading hierarchy
165
+ for (let i = 1; i < headingLevels.length; i++) {
166
+ const previousLevel = headingLevels[i - 1];
167
+ const currentLevel = headingLevels[i];
168
+ if (currentLevel > previousLevel + 1) {
169
+ findings.push({
170
+ category: "a11y",
171
+ severity: "warning",
172
+ message: `Heading level skipped: h${previousLevel} followed by h${currentLevel}.`,
173
+ recommendation: `Use sequential heading levels (h${previousLevel} → h${previousLevel + 1}).`,
174
+ });
175
+ }
176
+ }
177
+ return findings;
178
+ }
179
+ async auditPerformance(session) {
180
+ const findings = [];
181
+ await session.send("Performance.enable");
182
+ const { metrics } = await session.send("Performance.getMetrics");
183
+ await session.send("Performance.disable");
184
+ const metricMap = new Map();
185
+ for (const metric of metrics) {
186
+ metricMap.set(metric.name, metric.value);
187
+ }
188
+ // DOM node count
189
+ const domNodeCount = metricMap.get("Nodes");
190
+ if (domNodeCount !== undefined) {
191
+ if (domNodeCount > 3000) {
192
+ findings.push({
193
+ category: "performance",
194
+ severity: "error",
195
+ message: `DOM has ${domNodeCount} nodes (exceeds 3000).`,
196
+ recommendation: "Reduce DOM complexity by removing unnecessary elements or using virtualization.",
197
+ });
198
+ }
199
+ else if (domNodeCount > 1500) {
200
+ findings.push({
201
+ category: "performance",
202
+ severity: "warning",
203
+ message: `DOM has ${domNodeCount} nodes (exceeds 1500).`,
204
+ recommendation: "Consider simplifying the DOM structure for better performance.",
205
+ });
206
+ }
207
+ }
208
+ // Heap usage
209
+ const heapUsed = metricMap.get("JSHeapUsedSize");
210
+ const heapTotal = metricMap.get("JSHeapTotalSize");
211
+ if (heapUsed !== undefined && heapTotal !== undefined && heapTotal > 0) {
212
+ const heapPercent = (heapUsed / heapTotal) * 100;
213
+ if (heapPercent > 80) {
214
+ findings.push({
215
+ category: "performance",
216
+ severity: "warning",
217
+ message: `JS heap usage is ${heapPercent.toFixed(1)}% (${(heapUsed / 1024 / 1024).toFixed(1)}MB / ${(heapTotal / 1024 / 1024).toFixed(1)}MB).`,
218
+ recommendation: "Investigate memory usage and potential memory leaks.",
219
+ });
220
+ }
221
+ }
222
+ // Layout and style recalc counts
223
+ const layoutCount = metricMap.get("LayoutCount");
224
+ const recalcStyleCount = metricMap.get("RecalcStyleCount");
225
+ const scriptDuration = metricMap.get("ScriptDuration");
226
+ const taskDuration = metricMap.get("TaskDuration");
227
+ const performanceStats = [];
228
+ if (layoutCount !== undefined)
229
+ performanceStats.push(`layouts: ${layoutCount}`);
230
+ if (recalcStyleCount !== undefined)
231
+ performanceStats.push(`style recalcs: ${recalcStyleCount}`);
232
+ if (scriptDuration !== undefined)
233
+ performanceStats.push(`script: ${(scriptDuration * 1000).toFixed(0)}ms`);
234
+ if (taskDuration !== undefined)
235
+ performanceStats.push(`total tasks: ${(taskDuration * 1000).toFixed(0)}ms`);
236
+ if (performanceStats.length > 0) {
237
+ findings.push({
238
+ category: "performance",
239
+ severity: "info",
240
+ message: `Performance metrics: ${performanceStats.join(", ")}.`,
241
+ recommendation: "Use these metrics as a baseline for performance comparisons.",
242
+ });
243
+ }
244
+ return findings;
245
+ }
246
+ async auditSeo(page) {
247
+ const findings = [];
248
+ const seoData = await page.evaluate(() => {
249
+ const title = document.title;
250
+ const metaDescription = document
251
+ .querySelector('meta[name="description"]')
252
+ ?.getAttribute("content");
253
+ const metaViewport = document
254
+ .querySelector('meta[name="viewport"]')
255
+ ?.getAttribute("content");
256
+ const htmlLang = document.documentElement.getAttribute("lang");
257
+ const h1Elements = document.querySelectorAll("h1");
258
+ const headings = Array.from(document.querySelectorAll("h1, h2, h3, h4, h5, h6")).map((heading) => parseInt(heading.tagName.substring(1), 10));
259
+ return {
260
+ title: title || "",
261
+ metaDescription: metaDescription ?? null,
262
+ metaViewport: metaViewport ?? null,
263
+ htmlLang: htmlLang ?? null,
264
+ h1Count: h1Elements.length,
265
+ headingLevels: headings,
266
+ };
267
+ });
268
+ if (!seoData.title) {
269
+ findings.push({
270
+ category: "seo",
271
+ severity: "error",
272
+ message: "Page has no <title> tag or title is empty.",
273
+ recommendation: "Add a descriptive <title> element in the <head>.",
274
+ });
275
+ }
276
+ if (seoData.metaDescription === null) {
277
+ findings.push({
278
+ category: "seo",
279
+ severity: "warning",
280
+ message: "Page has no <meta name=\"description\"> tag.",
281
+ recommendation: "Add a meta description for search engine summaries.",
282
+ });
283
+ }
284
+ else if (seoData.metaDescription.trim() === "") {
285
+ findings.push({
286
+ category: "seo",
287
+ severity: "warning",
288
+ message: "Meta description is empty.",
289
+ recommendation: "Add meaningful content to the meta description.",
290
+ });
291
+ }
292
+ if (seoData.metaViewport === null) {
293
+ findings.push({
294
+ category: "seo",
295
+ severity: "warning",
296
+ message: "Page has no <meta name=\"viewport\"> tag.",
297
+ recommendation: 'Add <meta name="viewport" content="width=device-width, initial-scale=1.0">.',
298
+ });
299
+ }
300
+ if (seoData.htmlLang === null) {
301
+ findings.push({
302
+ category: "seo",
303
+ severity: "warning",
304
+ message: "HTML element has no lang attribute.",
305
+ recommendation: 'Add lang attribute to the <html> element, e.g. <html lang="en">.',
306
+ });
307
+ }
308
+ if (seoData.h1Count === 0) {
309
+ findings.push({
310
+ category: "seo",
311
+ severity: "warning",
312
+ message: "Page has no <h1> element.",
313
+ recommendation: "Add a single <h1> element for the page's main topic.",
314
+ });
315
+ }
316
+ else if (seoData.h1Count > 1) {
317
+ findings.push({
318
+ category: "seo",
319
+ severity: "warning",
320
+ message: `Page has ${seoData.h1Count} <h1> elements.`,
321
+ recommendation: "Use only one <h1> per page for clarity.",
322
+ });
323
+ }
324
+ // Check heading hierarchy
325
+ for (let i = 1; i < seoData.headingLevels.length; i++) {
326
+ const previousLevel = seoData.headingLevels[i - 1];
327
+ const currentLevel = seoData.headingLevels[i];
328
+ if (currentLevel > previousLevel + 1) {
329
+ findings.push({
330
+ category: "seo",
331
+ severity: "info",
332
+ message: `Heading hierarchy skip: h${previousLevel} → h${currentLevel}.`,
333
+ recommendation: "Use sequential heading levels for better SEO structure.",
334
+ });
335
+ break; // Only report the first skip
336
+ }
337
+ }
338
+ return findings;
339
+ }
340
+ async auditContrast(page, session) {
341
+ const findings = [];
342
+ const MAX_TEXT_ELEMENTS_TO_CHECK = 50;
343
+ // Get text elements with their computed styles via page.evaluate
344
+ const textElementData = await page.evaluate((maxElements) => {
345
+ const elements = [];
346
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
347
+ acceptNode: (node) => {
348
+ const text = node.textContent?.trim();
349
+ if (!text || text.length === 0)
350
+ return NodeFilter.FILTER_REJECT;
351
+ return NodeFilter.FILTER_ACCEPT;
352
+ },
353
+ });
354
+ let count = 0;
355
+ while (walker.nextNode() && count < maxElements) {
356
+ const textNode = walker.currentNode;
357
+ const parentElement = textNode.parentElement;
358
+ if (!parentElement)
359
+ continue;
360
+ const computedStyle = window.getComputedStyle(parentElement);
361
+ const color = computedStyle.color;
362
+ const backgroundColor = computedStyle.backgroundColor;
363
+ // Skip transparent backgrounds (they inherit from parent)
364
+ if (backgroundColor === "rgba(0, 0, 0, 0)" ||
365
+ backgroundColor === "transparent") {
366
+ continue;
367
+ }
368
+ elements.push({
369
+ text: (textNode.textContent?.trim() ?? "").substring(0, 50),
370
+ color,
371
+ backgroundColor,
372
+ fontSize: computedStyle.fontSize,
373
+ fontWeight: computedStyle.fontWeight,
374
+ selector: parentElement.tagName.toLowerCase() +
375
+ (parentElement.id ? `#${parentElement.id}` : "") +
376
+ (parentElement.className
377
+ ? `.${parentElement.className.split(" ").join(".")}`
378
+ : ""),
379
+ });
380
+ count++;
381
+ }
382
+ return elements;
383
+ }, MAX_TEXT_ELEMENTS_TO_CHECK);
384
+ for (const elementData of textElementData) {
385
+ const foregroundRgb = parseRgbColor(elementData.color);
386
+ const backgroundRgb = parseRgbColor(elementData.backgroundColor);
387
+ if (!foregroundRgb || !backgroundRgb)
388
+ continue;
389
+ const ratio = contrastRatio(foregroundRgb, backgroundRgb);
390
+ // Determine if text is "large" per WCAG
391
+ const fontSizePx = parseFloat(elementData.fontSize);
392
+ const fontWeightNumeric = elementData.fontWeight === "bold"
393
+ ? 700
394
+ : parseInt(elementData.fontWeight, 10) || 400;
395
+ const isLargeText = fontSizePx >= 24 || (fontSizePx >= 18.66 && fontWeightNumeric >= 700);
396
+ const minimumRatio = isLargeText ? 3 : 4.5;
397
+ if (ratio < minimumRatio) {
398
+ findings.push({
399
+ category: "contrast",
400
+ severity: ratio < 3 ? "error" : "warning",
401
+ message: `Low contrast ratio ${ratio.toFixed(2)}:1 (minimum ${minimumRatio}:1 for ${isLargeText ? "large" : "normal"} text). Text: "${elementData.text}".`,
402
+ element: elementData.selector,
403
+ recommendation: `Increase contrast between text color (${elementData.color}) and background (${elementData.backgroundColor}).`,
404
+ });
405
+ }
406
+ }
407
+ return findings;
408
+ }
409
+ async auditLinks(page) {
410
+ const findings = [];
411
+ const MAX_LINKS_TO_CHECK = 50;
412
+ const LINK_TIMEOUT_MS = 3000;
413
+ // Collect all link hrefs from the page
414
+ const linkData = await page.evaluate(() => {
415
+ const anchors = document.querySelectorAll("a[href]");
416
+ return Array.from(anchors).map((anchor) => ({
417
+ href: anchor.href,
418
+ text: anchor.textContent?.trim().substring(0, 50) || "(no text)",
419
+ }));
420
+ });
421
+ // Filter to checkable links and deduplicate
422
+ const checkedUrls = new Set();
423
+ const linksToCheck = [];
424
+ for (const link of linkData) {
425
+ if (linksToCheck.length >= MAX_LINKS_TO_CHECK)
426
+ break;
427
+ // Skip non-HTTP links
428
+ if (!link.href.startsWith("http://") &&
429
+ !link.href.startsWith("https://")) {
430
+ continue;
431
+ }
432
+ // Skip already-checked URLs
433
+ if (checkedUrls.has(link.href))
434
+ continue;
435
+ checkedUrls.add(link.href);
436
+ linksToCheck.push(link);
437
+ }
438
+ // Check all links concurrently
439
+ const checkResults = await Promise.allSettled(linksToCheck.map(async (link) => {
440
+ const controller = new AbortController();
441
+ const timeoutId = setTimeout(() => controller.abort(), LINK_TIMEOUT_MS);
442
+ try {
443
+ const response = await fetch(link.href, {
444
+ method: "HEAD",
445
+ signal: controller.signal,
446
+ redirect: "follow",
447
+ });
448
+ return { link, status: response.status, ok: response.ok };
449
+ }
450
+ catch {
451
+ return { link, status: 0, ok: false, timeout: true };
452
+ }
453
+ finally {
454
+ clearTimeout(timeoutId);
455
+ }
456
+ }));
457
+ for (const result of checkResults) {
458
+ if (result.status !== "fulfilled")
459
+ continue;
460
+ const { link, status, ok } = result.value;
461
+ if (!ok && status >= 400) {
462
+ findings.push({
463
+ category: "links",
464
+ severity: status >= 500 ? "error" : "warning",
465
+ message: `Broken link (HTTP ${status}): ${link.href}`,
466
+ element: `"${link.text}"`,
467
+ recommendation: "Fix or remove the broken link.",
468
+ });
469
+ }
470
+ }
471
+ return findings;
472
+ }
473
+ }
474
+ //# sourceMappingURL=auditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auditor.js","sourceRoot":"","sources":["../../src/dev/auditor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { PageManager } from "../browser/page-manager.js";
2
+ import type { ReloadEvent } from "../types/page-representation.js";
3
+ import { type StaticServerInfo } from "./static-server.js";
4
+ export interface DevServeOptions {
5
+ directoryPath: string;
6
+ port?: number;
7
+ watch: boolean;
8
+ pageManager: PageManager;
9
+ /** Use polling for file watching. Slower but avoids inotify limits. */
10
+ usePolling?: boolean;
11
+ }
12
+ export declare class DevModeState {
13
+ private staticServer;
14
+ private fileWatcher;
15
+ private pendingReloadEvent;
16
+ private reloadInProgress;
17
+ startServing(options: DevServeOptions): Promise<StaticServerInfo>;
18
+ stopAll(): Promise<void>;
19
+ consumePendingReloadEvent(): ReloadEvent | null;
20
+ isServing(): boolean;
21
+ getServerInfo(): StaticServerInfo | null;
22
+ private handleFilesChanged;
23
+ }
24
+ //# sourceMappingURL=dev-mode-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-mode-state.d.ts","sourceRoot":"","sources":["../../src/dev/dev-mode-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAIzE,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;IACzB,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,gBAAgB,CAA8B;IAEhD,YAAY,CAChB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,gBAAgB,CAAC;IAsBtB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAW9B,yBAAyB,IAAI,WAAW,GAAG,IAAI;IAM/C,SAAS,IAAI,OAAO;IAIpB,aAAa,IAAI,gBAAgB,GAAG,IAAI;IAIxC,OAAO,CAAC,kBAAkB;CAiD3B"}
@@ -0,0 +1,93 @@
1
+ import { StaticServer } from "./static-server.js";
2
+ import { FileWatcher } from "./file-watcher.js";
3
+ import { logger } from "../utils/logger.js";
4
+ export class DevModeState {
5
+ staticServer = new StaticServer();
6
+ fileWatcher = new FileWatcher();
7
+ pendingReloadEvent = null;
8
+ reloadInProgress = null;
9
+ async startServing(options) {
10
+ // Stop any existing serving session first
11
+ await this.stopAll();
12
+ const serverInfo = await this.staticServer.start({
13
+ directoryPath: options.directoryPath,
14
+ port: options.port,
15
+ });
16
+ if (options.watch) {
17
+ await this.fileWatcher.start({
18
+ directoryPath: options.directoryPath,
19
+ onFilesChanged: (changedFiles) => {
20
+ this.handleFilesChanged(changedFiles, options.pageManager);
21
+ },
22
+ usePolling: options.usePolling,
23
+ });
24
+ }
25
+ return serverInfo;
26
+ }
27
+ async stopAll() {
28
+ if (this.fileWatcher.isWatching()) {
29
+ await this.fileWatcher.stop();
30
+ }
31
+ if (this.staticServer.isRunning()) {
32
+ await this.staticServer.stop();
33
+ }
34
+ this.pendingReloadEvent = null;
35
+ this.reloadInProgress = null;
36
+ }
37
+ consumePendingReloadEvent() {
38
+ const event = this.pendingReloadEvent;
39
+ this.pendingReloadEvent = null;
40
+ return event;
41
+ }
42
+ isServing() {
43
+ return this.staticServer.isRunning();
44
+ }
45
+ getServerInfo() {
46
+ return this.staticServer.getInfo();
47
+ }
48
+ handleFilesChanged(changedFiles, pageManager) {
49
+ // Merge with any existing pending event (accumulate files)
50
+ if (this.pendingReloadEvent) {
51
+ const existingFiles = new Set(this.pendingReloadEvent.files_changed);
52
+ for (const file of changedFiles) {
53
+ existingFiles.add(file);
54
+ }
55
+ this.pendingReloadEvent = {
56
+ trigger: "file_change",
57
+ files_changed: [...existingFiles],
58
+ timestamp: new Date().toISOString(),
59
+ };
60
+ }
61
+ else {
62
+ this.pendingReloadEvent = {
63
+ trigger: "file_change",
64
+ files_changed: changedFiles,
65
+ timestamp: new Date().toISOString(),
66
+ };
67
+ }
68
+ // Reload the active page if not already reloading
69
+ if (this.reloadInProgress) {
70
+ logger.debug("Reload already in progress, skipping duplicate reload");
71
+ return;
72
+ }
73
+ if (!pageManager.hasPages()) {
74
+ logger.warn("No active page to reload after file change");
75
+ return;
76
+ }
77
+ const page = pageManager.getActivePage();
78
+ this.reloadInProgress = page
79
+ .reload({ waitUntil: "load" })
80
+ .then(() => {
81
+ logger.info("Page reloaded after file change", {
82
+ files: changedFiles,
83
+ });
84
+ })
85
+ .catch((error) => {
86
+ logger.warn("Failed to reload page after file change", error);
87
+ })
88
+ .finally(() => {
89
+ this.reloadInProgress = null;
90
+ });
91
+ }
92
+ }
93
+ //# sourceMappingURL=dev-mode-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-mode-state.js","sourceRoot":"","sources":["../../src/dev/dev-mode-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface FileWatcherOptions {
2
+ directoryPath: string;
3
+ onFilesChanged: (changedFiles: string[]) => void;
4
+ debounceMs?: number;
5
+ /** Use polling instead of native file system events. Slower but avoids inotify limits. */
6
+ usePolling?: boolean;
7
+ }
8
+ export declare class FileWatcher {
9
+ private watcher;
10
+ private pendingChanges;
11
+ private debounceTimer;
12
+ private directoryPath;
13
+ private onFilesChanged;
14
+ private debounceMs;
15
+ start(options: FileWatcherOptions): Promise<void>;
16
+ stop(): Promise<void>;
17
+ isWatching(): boolean;
18
+ private flushPendingChanges;
19
+ }
20
+ //# sourceMappingURL=file-watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-watcher.d.ts","sourceRoot":"","sources":["../../src/dev/file-watcher.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAID,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,cAAc,CAAmD;IACzE,OAAO,CAAC,UAAU,CAAuB;IAEnC,KAAK,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+CjD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B,UAAU,IAAI,OAAO;IAIrB,OAAO,CAAC,mBAAmB;CAY5B"}