uxinspect 0.2.0 → 0.11.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 (493) hide show
  1. package/README.md +332 -22
  2. package/dist/a11y-filter.d.ts +15 -0
  3. package/dist/a11y-filter.d.ts.map +1 -0
  4. package/dist/a11y-filter.js +107 -0
  5. package/dist/a11y-filter.js.map +1 -0
  6. package/dist/ab-compare.d.ts +23 -0
  7. package/dist/ab-compare.d.ts.map +1 -0
  8. package/dist/ab-compare.js +340 -0
  9. package/dist/ab-compare.js.map +1 -0
  10. package/dist/ai-codegen.d.ts +30 -0
  11. package/dist/ai-codegen.d.ts.map +1 -0
  12. package/dist/ai-codegen.js +296 -0
  13. package/dist/ai-codegen.js.map +1 -0
  14. package/dist/ai-triage.d.ts +26 -0
  15. package/dist/ai-triage.d.ts.map +1 -0
  16. package/dist/ai-triage.js +207 -0
  17. package/dist/ai-triage.js.map +1 -0
  18. package/dist/amp.d.ts +32 -0
  19. package/dist/amp.d.ts.map +1 -0
  20. package/dist/amp.js +179 -0
  21. package/dist/amp.js.map +1 -0
  22. package/dist/animation-audit.d.ts +25 -0
  23. package/dist/animation-audit.d.ts.map +1 -0
  24. package/dist/animation-audit.js +296 -0
  25. package/dist/animation-audit.js.map +1 -0
  26. package/dist/api.d.ts +3 -0
  27. package/dist/api.d.ts.map +1 -0
  28. package/dist/api.js +85 -0
  29. package/dist/api.js.map +1 -0
  30. package/dist/aria-audit.d.ts +20 -0
  31. package/dist/aria-audit.d.ts.map +1 -0
  32. package/dist/aria-audit.js +445 -0
  33. package/dist/aria-audit.js.map +1 -0
  34. package/dist/assertions.d.ts +30 -0
  35. package/dist/assertions.d.ts.map +1 -0
  36. package/dist/assertions.js +342 -0
  37. package/dist/assertions.js.map +1 -0
  38. package/dist/autofix.d.ts +40 -0
  39. package/dist/autofix.d.ts.map +1 -0
  40. package/dist/autofix.js +244 -0
  41. package/dist/autofix.js.map +1 -0
  42. package/dist/badge.d.ts +27 -0
  43. package/dist/badge.d.ts.map +1 -0
  44. package/dist/badge.js +183 -0
  45. package/dist/badge.js.map +1 -0
  46. package/dist/baseline-drift.d.ts +43 -0
  47. package/dist/baseline-drift.d.ts.map +1 -0
  48. package/dist/baseline-drift.js +208 -0
  49. package/dist/baseline-drift.js.map +1 -0
  50. package/dist/bdd.d.ts +31 -0
  51. package/dist/bdd.d.ts.map +1 -0
  52. package/dist/bdd.js +316 -0
  53. package/dist/bdd.js.map +1 -0
  54. package/dist/bisect.d.ts +32 -0
  55. package/dist/bisect.d.ts.map +1 -0
  56. package/dist/bisect.js +253 -0
  57. package/dist/bisect.js.map +1 -0
  58. package/dist/budget-diff.d.ts +37 -0
  59. package/dist/budget-diff.d.ts.map +1 -0
  60. package/dist/budget-diff.js +273 -0
  61. package/dist/budget-diff.js.map +1 -0
  62. package/dist/budget-file.d.ts +15 -0
  63. package/dist/budget-file.d.ts.map +1 -0
  64. package/dist/budget-file.js +185 -0
  65. package/dist/budget-file.js.map +1 -0
  66. package/dist/bundle-size.d.ts +36 -0
  67. package/dist/bundle-size.d.ts.map +1 -0
  68. package/dist/bundle-size.js +347 -0
  69. package/dist/bundle-size.js.map +1 -0
  70. package/dist/cache-headers.d.ts +33 -0
  71. package/dist/cache-headers.d.ts.map +1 -0
  72. package/dist/cache-headers.js +270 -0
  73. package/dist/cache-headers.js.map +1 -0
  74. package/dist/canonical-audit.d.ts +19 -0
  75. package/dist/canonical-audit.d.ts.map +1 -0
  76. package/dist/canonical-audit.js +196 -0
  77. package/dist/canonical-audit.js.map +1 -0
  78. package/dist/chaos.d.ts +38 -0
  79. package/dist/chaos.d.ts.map +1 -0
  80. package/dist/chaos.js +348 -0
  81. package/dist/chaos.js.map +1 -0
  82. package/dist/cli.js +201 -23
  83. package/dist/cli.js.map +1 -1
  84. package/dist/clickjacking-audit.d.ts +18 -0
  85. package/dist/clickjacking-audit.d.ts.map +1 -0
  86. package/dist/clickjacking-audit.js +231 -0
  87. package/dist/clickjacking-audit.js.map +1 -0
  88. package/dist/cls-culprit.d.ts +36 -0
  89. package/dist/cls-culprit.d.ts.map +1 -0
  90. package/dist/cls-culprit.js +203 -0
  91. package/dist/cls-culprit.js.map +1 -0
  92. package/dist/cls-timeline.d.ts +30 -0
  93. package/dist/cls-timeline.d.ts.map +1 -0
  94. package/dist/cls-timeline.js +61 -0
  95. package/dist/cls-timeline.js.map +1 -0
  96. package/dist/codegen-converter.d.ts +19 -0
  97. package/dist/codegen-converter.d.ts.map +1 -0
  98. package/dist/codegen-converter.js +464 -0
  99. package/dist/codegen-converter.js.map +1 -0
  100. package/dist/compression.d.ts +14 -0
  101. package/dist/compression.d.ts.map +1 -0
  102. package/dist/compression.js +150 -0
  103. package/dist/compression.js.map +1 -0
  104. package/dist/console-errors.d.ts +24 -0
  105. package/dist/console-errors.d.ts.map +1 -0
  106. package/dist/console-errors.js +96 -0
  107. package/dist/console-errors.js.map +1 -0
  108. package/dist/content-quality.d.ts +34 -0
  109. package/dist/content-quality.d.ts.map +1 -0
  110. package/dist/content-quality.js +124 -0
  111. package/dist/content-quality.js.map +1 -0
  112. package/dist/contract-openapi.d.ts +74 -0
  113. package/dist/contract-openapi.d.ts.map +1 -0
  114. package/dist/contract-openapi.js +305 -0
  115. package/dist/contract-openapi.js.map +1 -0
  116. package/dist/cookie-banner.d.ts +27 -0
  117. package/dist/cookie-banner.d.ts.map +1 -0
  118. package/dist/cookie-banner.js +285 -0
  119. package/dist/cookie-banner.js.map +1 -0
  120. package/dist/cookie-flags-audit.d.ts +35 -0
  121. package/dist/cookie-flags-audit.d.ts.map +1 -0
  122. package/dist/cookie-flags-audit.js +167 -0
  123. package/dist/cookie-flags-audit.js.map +1 -0
  124. package/dist/cpu-throttle.d.ts +34 -0
  125. package/dist/cpu-throttle.d.ts.map +1 -0
  126. package/dist/cpu-throttle.js +149 -0
  127. package/dist/cpu-throttle.js.map +1 -0
  128. package/dist/crawl.d.ts +29 -0
  129. package/dist/crawl.d.ts.map +1 -0
  130. package/dist/crawl.js +153 -0
  131. package/dist/crawl.js.map +1 -0
  132. package/dist/critical-css.d.ts +25 -0
  133. package/dist/critical-css.d.ts.map +1 -0
  134. package/dist/critical-css.js +353 -0
  135. package/dist/critical-css.js.map +1 -0
  136. package/dist/cross-browser.d.ts +44 -0
  137. package/dist/cross-browser.d.ts.map +1 -0
  138. package/dist/cross-browser.js +300 -0
  139. package/dist/cross-browser.js.map +1 -0
  140. package/dist/csrf-audit.d.ts +33 -0
  141. package/dist/csrf-audit.d.ts.map +1 -0
  142. package/dist/csrf-audit.js +276 -0
  143. package/dist/csrf-audit.js.map +1 -0
  144. package/dist/css-coverage.d.ts +20 -0
  145. package/dist/css-coverage.d.ts.map +1 -0
  146. package/dist/css-coverage.js +91 -0
  147. package/dist/css-coverage.js.map +1 -0
  148. package/dist/csv-exporter.d.ts +34 -0
  149. package/dist/csv-exporter.d.ts.map +1 -0
  150. package/dist/csv-exporter.js +241 -0
  151. package/dist/csv-exporter.js.map +1 -0
  152. package/dist/dark-mode-audit.d.ts +31 -0
  153. package/dist/dark-mode-audit.d.ts.map +1 -0
  154. package/dist/dark-mode-audit.js +236 -0
  155. package/dist/dark-mode-audit.js.map +1 -0
  156. package/dist/dead-images.d.ts +18 -0
  157. package/dist/dead-images.d.ts.map +1 -0
  158. package/dist/dead-images.js +236 -0
  159. package/dist/dead-images.js.map +1 -0
  160. package/dist/deadclicks.d.ts +19 -0
  161. package/dist/deadclicks.d.ts.map +1 -0
  162. package/dist/deadclicks.js +109 -0
  163. package/dist/deadclicks.js.map +1 -0
  164. package/dist/discord-formatter.d.ts +39 -0
  165. package/dist/discord-formatter.d.ts.map +1 -0
  166. package/dist/discord-formatter.js +191 -0
  167. package/dist/discord-formatter.js.map +1 -0
  168. package/dist/dom-audit.d.ts +23 -0
  169. package/dist/dom-audit.d.ts.map +1 -0
  170. package/dist/dom-audit.js +111 -0
  171. package/dist/dom-audit.js.map +1 -0
  172. package/dist/driver.d.ts.map +1 -1
  173. package/dist/driver.js +10 -0
  174. package/dist/driver.js.map +1 -1
  175. package/dist/error-page-audit.d.ts +26 -0
  176. package/dist/error-page-audit.d.ts.map +1 -0
  177. package/dist/error-page-audit.js +219 -0
  178. package/dist/error-page-audit.js.map +1 -0
  179. package/dist/event-listener-audit.d.ts +22 -0
  180. package/dist/event-listener-audit.d.ts.map +1 -0
  181. package/dist/event-listener-audit.js +156 -0
  182. package/dist/event-listener-audit.js.map +1 -0
  183. package/dist/exposed-paths.d.ts +21 -0
  184. package/dist/exposed-paths.d.ts.map +1 -0
  185. package/dist/exposed-paths.js +116 -0
  186. package/dist/exposed-paths.js.map +1 -0
  187. package/dist/favicon-audit.d.ts +28 -0
  188. package/dist/favicon-audit.d.ts.map +1 -0
  189. package/dist/favicon-audit.js +358 -0
  190. package/dist/favicon-audit.js.map +1 -0
  191. package/dist/flaky-detector.d.ts +32 -0
  192. package/dist/flaky-detector.d.ts.map +1 -0
  193. package/dist/flaky-detector.js +254 -0
  194. package/dist/flaky-detector.js.map +1 -0
  195. package/dist/flaky.d.ts +28 -0
  196. package/dist/flaky.d.ts.map +1 -0
  197. package/dist/flaky.js +106 -0
  198. package/dist/flaky.js.map +1 -0
  199. package/dist/focus-trap-audit.d.ts +29 -0
  200. package/dist/focus-trap-audit.d.ts.map +1 -0
  201. package/dist/focus-trap-audit.js +285 -0
  202. package/dist/focus-trap-audit.js.map +1 -0
  203. package/dist/font-loading.d.ts +29 -0
  204. package/dist/font-loading.d.ts.map +1 -0
  205. package/dist/font-loading.js +216 -0
  206. package/dist/font-loading.js.map +1 -0
  207. package/dist/forms-audit.d.ts +23 -0
  208. package/dist/forms-audit.d.ts.map +1 -0
  209. package/dist/forms-audit.js +147 -0
  210. package/dist/forms-audit.js.map +1 -0
  211. package/dist/github-annotations.d.ts +17 -0
  212. package/dist/github-annotations.d.ts.map +1 -0
  213. package/dist/github-annotations.js +264 -0
  214. package/dist/github-annotations.js.map +1 -0
  215. package/dist/graphql.d.ts +60 -0
  216. package/dist/graphql.d.ts.map +1 -0
  217. package/dist/graphql.js +188 -0
  218. package/dist/graphql.js.map +1 -0
  219. package/dist/har-waterfall.d.ts +37 -0
  220. package/dist/har-waterfall.d.ts.map +1 -0
  221. package/dist/har-waterfall.js +376 -0
  222. package/dist/har-waterfall.js.map +1 -0
  223. package/dist/heading-hierarchy.d.ts +20 -0
  224. package/dist/heading-hierarchy.d.ts.map +1 -0
  225. package/dist/heading-hierarchy.js +112 -0
  226. package/dist/heading-hierarchy.js.map +1 -0
  227. package/dist/headless-detect.d.ts +22 -0
  228. package/dist/headless-detect.d.ts.map +1 -0
  229. package/dist/headless-detect.js +167 -0
  230. package/dist/headless-detect.js.map +1 -0
  231. package/dist/history-timeline.d.ts +13 -0
  232. package/dist/history-timeline.d.ts.map +1 -0
  233. package/dist/history-timeline.js +327 -0
  234. package/dist/history-timeline.js.map +1 -0
  235. package/dist/hreflang-audit.d.ts +26 -0
  236. package/dist/hreflang-audit.d.ts.map +1 -0
  237. package/dist/hreflang-audit.js +273 -0
  238. package/dist/hreflang-audit.js.map +1 -0
  239. package/dist/hydration-audit.d.ts +21 -0
  240. package/dist/hydration-audit.d.ts.map +1 -0
  241. package/dist/hydration-audit.js +277 -0
  242. package/dist/hydration-audit.js.map +1 -0
  243. package/dist/image-audit.d.ts +41 -0
  244. package/dist/image-audit.d.ts.map +1 -0
  245. package/dist/image-audit.js +229 -0
  246. package/dist/image-audit.js.map +1 -0
  247. package/dist/index.d.ts +119 -0
  248. package/dist/index.d.ts.map +1 -1
  249. package/dist/index.js +708 -2
  250. package/dist/index.js.map +1 -1
  251. package/dist/init-wizard.d.ts +33 -0
  252. package/dist/init-wizard.d.ts.map +1 -0
  253. package/dist/init-wizard.js +289 -0
  254. package/dist/init-wizard.js.map +1 -0
  255. package/dist/inp-audit.d.ts +26 -0
  256. package/dist/inp-audit.d.ts.map +1 -0
  257. package/dist/inp-audit.js +202 -0
  258. package/dist/inp-audit.js.map +1 -0
  259. package/dist/js-coverage.d.ts +20 -0
  260. package/dist/js-coverage.d.ts.map +1 -0
  261. package/dist/js-coverage.js +81 -0
  262. package/dist/js-coverage.js.map +1 -0
  263. package/dist/json-schema.d.ts +27 -0
  264. package/dist/json-schema.d.ts.map +1 -0
  265. package/dist/json-schema.js +284 -0
  266. package/dist/json-schema.js.map +1 -0
  267. package/dist/keyboard.d.ts +21 -0
  268. package/dist/keyboard.d.ts.map +1 -0
  269. package/dist/keyboard.js +119 -0
  270. package/dist/keyboard.js.map +1 -0
  271. package/dist/lang-audit.d.ts +24 -0
  272. package/dist/lang-audit.d.ts.map +1 -0
  273. package/dist/lang-audit.js +141 -0
  274. package/dist/lang-audit.js.map +1 -0
  275. package/dist/lcp-element.d.ts +22 -0
  276. package/dist/lcp-element.d.ts.map +1 -0
  277. package/dist/lcp-element.js +240 -0
  278. package/dist/lcp-element.js.map +1 -0
  279. package/dist/longtasks.d.ts +38 -0
  280. package/dist/longtasks.d.ts.map +1 -0
  281. package/dist/longtasks.js +97 -0
  282. package/dist/longtasks.js.map +1 -0
  283. package/dist/mailbox.d.ts +35 -0
  284. package/dist/mailbox.d.ts.map +1 -0
  285. package/dist/mailbox.js +207 -0
  286. package/dist/mailbox.js.map +1 -0
  287. package/dist/media-audit.d.ts +20 -0
  288. package/dist/media-audit.d.ts.map +1 -0
  289. package/dist/media-audit.js +182 -0
  290. package/dist/media-audit.js.map +1 -0
  291. package/dist/metrics-exporter.d.ts +23 -0
  292. package/dist/metrics-exporter.d.ts.map +1 -0
  293. package/dist/metrics-exporter.js +297 -0
  294. package/dist/metrics-exporter.js.map +1 -0
  295. package/dist/mixed-content.d.ts +19 -0
  296. package/dist/mixed-content.d.ts.map +1 -0
  297. package/dist/mixed-content.js +86 -0
  298. package/dist/mixed-content.js.map +1 -0
  299. package/dist/motion-prefs.d.ts +21 -0
  300. package/dist/motion-prefs.d.ts.map +1 -0
  301. package/dist/motion-prefs.js +170 -0
  302. package/dist/motion-prefs.js.map +1 -0
  303. package/dist/open-graph.d.ts +40 -0
  304. package/dist/open-graph.d.ts.map +1 -0
  305. package/dist/open-graph.js +200 -0
  306. package/dist/open-graph.js.map +1 -0
  307. package/dist/orphan-assets.d.ts +17 -0
  308. package/dist/orphan-assets.d.ts.map +1 -0
  309. package/dist/orphan-assets.js +174 -0
  310. package/dist/orphan-assets.js.map +1 -0
  311. package/dist/page-object.d.ts +18 -0
  312. package/dist/page-object.d.ts.map +1 -0
  313. package/dist/page-object.js +346 -0
  314. package/dist/page-object.js.map +1 -0
  315. package/dist/pagination-audit.d.ts +24 -0
  316. package/dist/pagination-audit.d.ts.map +1 -0
  317. package/dist/pagination-audit.js +285 -0
  318. package/dist/pagination-audit.js.map +1 -0
  319. package/dist/passive-security.d.ts +19 -0
  320. package/dist/passive-security.d.ts.map +1 -0
  321. package/dist/passive-security.js +149 -0
  322. package/dist/passive-security.js.map +1 -0
  323. package/dist/pr-comment.d.ts +13 -0
  324. package/dist/pr-comment.d.ts.map +1 -0
  325. package/dist/pr-comment.js +316 -0
  326. package/dist/pr-comment.js.map +1 -0
  327. package/dist/precommit.d.ts +24 -0
  328. package/dist/precommit.d.ts.map +1 -0
  329. package/dist/precommit.js +239 -0
  330. package/dist/precommit.js.map +1 -0
  331. package/dist/prerender-audit.d.ts +22 -0
  332. package/dist/prerender-audit.d.ts.map +1 -0
  333. package/dist/prerender-audit.js +158 -0
  334. package/dist/prerender-audit.js.map +1 -0
  335. package/dist/print-audit.d.ts +21 -0
  336. package/dist/print-audit.d.ts.map +1 -0
  337. package/dist/print-audit.js +281 -0
  338. package/dist/print-audit.js.map +1 -0
  339. package/dist/protocol-audit.d.ts +17 -0
  340. package/dist/protocol-audit.d.ts.map +1 -0
  341. package/dist/protocol-audit.js +128 -0
  342. package/dist/protocol-audit.js.map +1 -0
  343. package/dist/reading-level.d.ts +37 -0
  344. package/dist/reading-level.d.ts.map +1 -0
  345. package/dist/reading-level.js +220 -0
  346. package/dist/reading-level.js.map +1 -0
  347. package/dist/redirects.d.ts +24 -0
  348. package/dist/redirects.d.ts.map +1 -0
  349. package/dist/redirects.js +119 -0
  350. package/dist/redirects.js.map +1 -0
  351. package/dist/report.d.ts +1 -1
  352. package/dist/report.d.ts.map +1 -1
  353. package/dist/report.js +736 -1
  354. package/dist/report.js.map +1 -1
  355. package/dist/reporter-plugin.d.ts +32 -0
  356. package/dist/reporter-plugin.d.ts.map +1 -0
  357. package/dist/reporter-plugin.js +120 -0
  358. package/dist/reporter-plugin.js.map +1 -0
  359. package/dist/resource-hints.d.ts +23 -0
  360. package/dist/resource-hints.d.ts.map +1 -0
  361. package/dist/resource-hints.js +225 -0
  362. package/dist/resource-hints.js.map +1 -0
  363. package/dist/retire.d.ts +22 -0
  364. package/dist/retire.d.ts.map +1 -0
  365. package/dist/retire.js +140 -0
  366. package/dist/retire.js.map +1 -0
  367. package/dist/retry.d.ts +20 -0
  368. package/dist/retry.d.ts.map +1 -0
  369. package/dist/retry.js +120 -0
  370. package/dist/retry.js.map +1 -0
  371. package/dist/robots-audit.d.ts +24 -0
  372. package/dist/robots-audit.d.ts.map +1 -0
  373. package/dist/robots-audit.js +206 -0
  374. package/dist/robots-audit.js.map +1 -0
  375. package/dist/rum.d.ts +35 -0
  376. package/dist/rum.d.ts.map +1 -0
  377. package/dist/rum.js +219 -0
  378. package/dist/rum.js.map +1 -0
  379. package/dist/schedule.d.ts +30 -0
  380. package/dist/schedule.d.ts.map +1 -0
  381. package/dist/schedule.js +238 -0
  382. package/dist/schedule.js.map +1 -0
  383. package/dist/secret-scan.d.ts +24 -0
  384. package/dist/secret-scan.d.ts.map +1 -0
  385. package/dist/secret-scan.js +202 -0
  386. package/dist/secret-scan.js.map +1 -0
  387. package/dist/service-worker.d.ts +26 -0
  388. package/dist/service-worker.d.ts.map +1 -0
  389. package/dist/service-worker.js +179 -0
  390. package/dist/service-worker.js.map +1 -0
  391. package/dist/shard.d.ts +14 -0
  392. package/dist/shard.d.ts.map +1 -0
  393. package/dist/shard.js +72 -0
  394. package/dist/shard.js.map +1 -0
  395. package/dist/sitemap-flows.d.ts +13 -0
  396. package/dist/sitemap-flows.d.ts.map +1 -0
  397. package/dist/sitemap-flows.js +157 -0
  398. package/dist/sitemap-flows.js.map +1 -0
  399. package/dist/sitemap.d.ts +27 -0
  400. package/dist/sitemap.d.ts.map +1 -0
  401. package/dist/sitemap.js +137 -0
  402. package/dist/sitemap.js.map +1 -0
  403. package/dist/slack-formatter.d.ts +35 -0
  404. package/dist/slack-formatter.d.ts.map +1 -0
  405. package/dist/slack-formatter.js +193 -0
  406. package/dist/slack-formatter.js.map +1 -0
  407. package/dist/sourcemap-scan.d.ts +24 -0
  408. package/dist/sourcemap-scan.d.ts.map +1 -0
  409. package/dist/sourcemap-scan.js +232 -0
  410. package/dist/sourcemap-scan.js.map +1 -0
  411. package/dist/sri-audit.d.ts +23 -0
  412. package/dist/sri-audit.d.ts.map +1 -0
  413. package/dist/sri-audit.js +180 -0
  414. package/dist/sri-audit.js.map +1 -0
  415. package/dist/storage-audit.d.ts +28 -0
  416. package/dist/storage-audit.d.ts.map +1 -0
  417. package/dist/storage-audit.js +263 -0
  418. package/dist/storage-audit.js.map +1 -0
  419. package/dist/storybook.d.ts +48 -0
  420. package/dist/storybook.d.ts.map +1 -0
  421. package/dist/storybook.js +191 -0
  422. package/dist/storybook.js.map +1 -0
  423. package/dist/structured-data.d.ts +25 -0
  424. package/dist/structured-data.d.ts.map +1 -0
  425. package/dist/structured-data.js +164 -0
  426. package/dist/structured-data.js.map +1 -0
  427. package/dist/svg-audit.d.ts +20 -0
  428. package/dist/svg-audit.d.ts.map +1 -0
  429. package/dist/svg-audit.js +213 -0
  430. package/dist/svg-audit.js.map +1 -0
  431. package/dist/table-audit.d.ts +18 -0
  432. package/dist/table-audit.d.ts.map +1 -0
  433. package/dist/table-audit.js +188 -0
  434. package/dist/table-audit.js.map +1 -0
  435. package/dist/teams-formatter.d.ts +66 -0
  436. package/dist/teams-formatter.d.ts.map +1 -0
  437. package/dist/teams-formatter.js +194 -0
  438. package/dist/teams-formatter.js.map +1 -0
  439. package/dist/third-party.d.ts +35 -0
  440. package/dist/third-party.d.ts.map +1 -0
  441. package/dist/third-party.js +175 -0
  442. package/dist/third-party.js.map +1 -0
  443. package/dist/tls.d.ts +33 -0
  444. package/dist/tls.d.ts.map +1 -0
  445. package/dist/tls.js +122 -0
  446. package/dist/tls.js.map +1 -0
  447. package/dist/touchtargets.d.ts +22 -0
  448. package/dist/touchtargets.d.ts.map +1 -0
  449. package/dist/touchtargets.js +80 -0
  450. package/dist/touchtargets.js.map +1 -0
  451. package/dist/tracker-sniff.d.ts +25 -0
  452. package/dist/tracker-sniff.d.ts.map +1 -0
  453. package/dist/tracker-sniff.js +355 -0
  454. package/dist/tracker-sniff.js.map +1 -0
  455. package/dist/types.d.ts +265 -1
  456. package/dist/types.d.ts.map +1 -1
  457. package/dist/visual-mask.d.ts +33 -0
  458. package/dist/visual-mask.d.ts.map +1 -0
  459. package/dist/visual-mask.js +102 -0
  460. package/dist/visual-mask.js.map +1 -0
  461. package/dist/visual-ssim.d.ts +26 -0
  462. package/dist/visual-ssim.d.ts.map +1 -0
  463. package/dist/visual-ssim.js +153 -0
  464. package/dist/visual-ssim.js.map +1 -0
  465. package/dist/watch-mode.d.ts +10 -0
  466. package/dist/watch-mode.d.ts.map +1 -0
  467. package/dist/watch-mode.js +156 -0
  468. package/dist/watch-mode.js.map +1 -0
  469. package/dist/web-worker-audit.d.ts +27 -0
  470. package/dist/web-worker-audit.d.ts.map +1 -0
  471. package/dist/web-worker-audit.js +324 -0
  472. package/dist/web-worker-audit.js.map +1 -0
  473. package/dist/webfonts.d.ts +26 -0
  474. package/dist/webfonts.d.ts.map +1 -0
  475. package/dist/webfonts.js +244 -0
  476. package/dist/webfonts.js.map +1 -0
  477. package/dist/webhook-reporter.d.ts +20 -0
  478. package/dist/webhook-reporter.d.ts.map +1 -0
  479. package/dist/webhook-reporter.js +124 -0
  480. package/dist/webhook-reporter.js.map +1 -0
  481. package/dist/websocket.d.ts +39 -0
  482. package/dist/websocket.d.ts.map +1 -0
  483. package/dist/websocket.js +233 -0
  484. package/dist/websocket.js.map +1 -0
  485. package/dist/worker-runtime.d.ts +129 -0
  486. package/dist/worker-runtime.d.ts.map +1 -0
  487. package/dist/worker-runtime.js +414 -0
  488. package/dist/worker-runtime.js.map +1 -0
  489. package/dist/zindex-audit.d.ts +28 -0
  490. package/dist/zindex-audit.d.ts.map +1 -0
  491. package/dist/zindex-audit.js +291 -0
  492. package/dist/zindex-audit.js.map +1 -0
  493. package/package.json +10 -2
package/dist/report.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { promises as fs } from 'node:fs';
2
2
  import path from 'node:path';
3
+ import crypto from 'node:crypto';
3
4
  const _pathRef = path;
4
5
  export async function writeReport(result, outDir, reporters = ['html', 'json']) {
5
6
  await fs.mkdir(outDir, { recursive: true });
@@ -16,8 +17,247 @@ export async function writeReport(result, outDir, reporters = ['html', 'json'])
16
17
  if (reporters.includes('sarif')) {
17
18
  await fs.writeFile(path.join(outDir, 'sarif.json'), JSON.stringify(renderSARIF(result), null, 2));
18
19
  }
20
+ if (reporters.includes('allure')) {
21
+ await writeAllure(result, outDir);
22
+ }
23
+ if (reporters.includes('tap')) {
24
+ await fs.writeFile(path.join(outDir, 'report.tap'), renderTAP(result));
25
+ }
19
26
  return htmlPath;
20
27
  }
28
+ async function writeAllure(r, outDir) {
29
+ const allureDir = path.join(outDir, 'allure-results');
30
+ await fs.mkdir(allureDir, { recursive: true });
31
+ const startBase = new Date(r.startedAt).getTime();
32
+ const stopBase = new Date(r.finishedAt).getTime();
33
+ const safeUrl = r.url.replace(/[^a-zA-Z0-9]+/g, '_');
34
+ const hashId = (s) => crypto.createHash('md5').update(s).digest('hex');
35
+ const writeResult = async (obj) => {
36
+ const uuid = obj.uuid ?? crypto.randomUUID();
37
+ obj.uuid = uuid;
38
+ await fs.writeFile(path.join(allureDir, `${uuid}-result.json`), JSON.stringify(obj, null, 2));
39
+ };
40
+ for (const flow of r.flows) {
41
+ const flowStart = startBase;
42
+ let cursor = flowStart;
43
+ const steps = flow.steps.map((s) => {
44
+ const stepStart = cursor;
45
+ const stepStop = cursor + (s.durationMs || 0);
46
+ cursor = stepStop;
47
+ return {
48
+ name: describeStep(s.step),
49
+ status: s.passed ? 'passed' : 'failed',
50
+ stage: 'finished',
51
+ start: stepStart,
52
+ stop: stepStop,
53
+ ...(s.error ? { statusDetails: { message: s.error } } : {}),
54
+ };
55
+ });
56
+ const flowStop = cursor > flowStart ? cursor : stopBase;
57
+ const status = flow.passed ? 'passed' : flow.error ? 'broken' : 'failed';
58
+ await writeResult({
59
+ uuid: crypto.randomUUID(),
60
+ historyId: hashId(`flow:${flow.name}`),
61
+ name: flow.name,
62
+ fullName: `uxinspect.${safeUrl}.${flow.name}`,
63
+ status,
64
+ stage: 'finished',
65
+ start: flowStart,
66
+ stop: flowStop,
67
+ labels: [
68
+ { name: 'suite', value: r.url },
69
+ { name: 'severity', value: 'normal' },
70
+ { name: 'feature', value: 'flow' },
71
+ ],
72
+ steps,
73
+ ...(flow.error ? { statusDetails: { message: flow.error } } : {}),
74
+ });
75
+ }
76
+ for (const a of r.a11y ?? []) {
77
+ const critical = a.violations.filter((v) => v.impact === 'critical' || v.impact === 'serious');
78
+ const failed = critical.length > 0;
79
+ const name = `a11y: ${a.page}`;
80
+ await writeResult({
81
+ uuid: crypto.randomUUID(),
82
+ historyId: hashId(`a11y:${a.page}`),
83
+ name,
84
+ fullName: `uxinspect.${safeUrl}.a11y.${a.page}`,
85
+ status: failed ? 'failed' : 'passed',
86
+ stage: 'finished',
87
+ start: startBase,
88
+ stop: stopBase,
89
+ labels: [
90
+ { name: 'suite', value: r.url },
91
+ { name: 'severity', value: failed ? 'critical' : 'normal' },
92
+ { name: 'feature', value: 'a11y' },
93
+ ],
94
+ steps: a.violations.map((v) => ({
95
+ name: `${v.id}: ${v.help}`,
96
+ status: v.impact === 'critical' || v.impact === 'serious' ? 'failed' : 'passed',
97
+ stage: 'finished',
98
+ start: startBase,
99
+ stop: stopBase,
100
+ })),
101
+ ...(failed
102
+ ? { statusDetails: { message: `${critical.length} critical/serious a11y violations` } }
103
+ : {}),
104
+ });
105
+ }
106
+ }
107
+ function renderTAP(r) {
108
+ const points = [];
109
+ for (const f of r.flows) {
110
+ points.push({
111
+ ok: f.passed,
112
+ desc: `flow: ${f.name}`,
113
+ message: f.error,
114
+ });
115
+ }
116
+ for (const a of r.a11y ?? []) {
117
+ const crit = a.violations.filter((v) => v.impact === 'critical' || v.impact === 'serious');
118
+ points.push({
119
+ ok: crit.length === 0,
120
+ desc: `a11y: ${a.page}`,
121
+ message: crit.length > 0 ? `${crit.length} critical/serious violations` : undefined,
122
+ });
123
+ }
124
+ for (const v of r.visual ?? []) {
125
+ points.push({
126
+ ok: v.passed,
127
+ desc: `visual: ${v.page} [${v.viewport}]`,
128
+ message: v.passed ? undefined : `diff ${v.diffPixels}px (${(v.diffRatio * 100).toFixed(2)}%)`,
129
+ });
130
+ }
131
+ for (const p of r.perf ?? []) {
132
+ const score = p.scores.performance;
133
+ points.push({
134
+ ok: score >= 50,
135
+ desc: `perf: ${p.page} (score=${score})`,
136
+ message: score < 50 ? `performance score ${score} below threshold 50` : undefined,
137
+ });
138
+ }
139
+ for (const s of r.seo ?? []) {
140
+ const issues = s.issues ?? [];
141
+ points.push({
142
+ ok: issues.length === 0,
143
+ desc: `seo: ${s.page ?? 'page'}`,
144
+ message: issues.length > 0 ? `${issues.length} seo issues` : undefined,
145
+ });
146
+ }
147
+ for (const l of r.links ?? []) {
148
+ const broken = l.broken ?? [];
149
+ points.push({
150
+ ok: broken.length === 0,
151
+ desc: `links: ${l.page ?? 'page'}`,
152
+ message: broken.length > 0 ? `${broken.length} broken links` : undefined,
153
+ });
154
+ }
155
+ for (const p of r.pwa ?? []) {
156
+ const passed = p.passed ?? true;
157
+ points.push({
158
+ ok: passed,
159
+ desc: `pwa: ${p.page ?? 'page'}`,
160
+ });
161
+ }
162
+ if (r.security) {
163
+ const missing = r.security.missing ?? [];
164
+ points.push({
165
+ ok: missing.length === 0,
166
+ desc: 'security: headers',
167
+ message: missing.length > 0 ? `${missing.length} missing headers` : undefined,
168
+ });
169
+ }
170
+ for (const v of r.retire ?? []) {
171
+ const vulns = v.vulnerabilities ?? [];
172
+ points.push({
173
+ ok: vulns.length === 0,
174
+ desc: `retire: ${v.url ?? 'script'}`,
175
+ message: vulns.length > 0 ? `${vulns.length} vulnerabilities` : undefined,
176
+ });
177
+ }
178
+ for (const f of r.apiFlows ?? []) {
179
+ points.push({ ok: f.passed, desc: `api: ${f.name}`, message: f.error });
180
+ }
181
+ for (const b of r.budget ?? []) {
182
+ const msg = b.message
183
+ ?? b.metric
184
+ ?? 'budget violation';
185
+ points.push({ ok: false, desc: `budget: ${msg}`, message: msg });
186
+ }
187
+ for (const d of r.deadClicks ?? []) {
188
+ const found = d.deadClicks ?? [];
189
+ points.push({
190
+ ok: found.length === 0,
191
+ desc: `deadClicks: ${d.page ?? 'page'}`,
192
+ message: found.length > 0 ? `${found.length} dead clicks` : undefined,
193
+ });
194
+ }
195
+ for (const t of r.touchTargets ?? []) {
196
+ const small = t.tooSmall ?? [];
197
+ points.push({
198
+ ok: small.length === 0,
199
+ desc: `touchTargets: ${t.page ?? 'page'}`,
200
+ message: small.length > 0 ? `${small.length} small targets` : undefined,
201
+ });
202
+ }
203
+ for (const k of r.keyboard ?? []) {
204
+ const issues = k.issues ?? [];
205
+ points.push({
206
+ ok: issues.length === 0,
207
+ desc: `keyboard: ${k.page ?? 'page'}`,
208
+ message: issues.length > 0 ? `${issues.length} keyboard issues` : undefined,
209
+ });
210
+ }
211
+ for (const ce of r.consoleErrors ?? []) {
212
+ const errs = ce.errors ?? [];
213
+ points.push({
214
+ ok: errs.length === 0,
215
+ desc: `consoleErrors: ${ce.page ?? 'page'}`,
216
+ message: errs.length > 0 ? `${errs.length} console errors` : undefined,
217
+ });
218
+ }
219
+ for (const mc of r.mixedContent ?? []) {
220
+ const items = mc.items ?? [];
221
+ points.push({
222
+ ok: items.length === 0,
223
+ desc: `mixedContent: ${mc.page ?? 'page'}`,
224
+ message: items.length > 0 ? `${items.length} mixed content items` : undefined,
225
+ });
226
+ }
227
+ const total = points.length;
228
+ const lines = ['TAP version 14', `1..${total}`];
229
+ points.forEach((p, i) => {
230
+ const n = i + 1;
231
+ const prefix = p.ok ? 'ok' : 'not ok';
232
+ lines.push(`${prefix} ${n} - ${escapeTapDesc(p.desc)}`);
233
+ if (!p.ok && p.message) {
234
+ lines.push(' ---');
235
+ lines.push(` message: '${escapeTapMsg(p.message)}'`);
236
+ lines.push(' ---');
237
+ }
238
+ });
239
+ return lines.join('\n') + '\n';
240
+ }
241
+ function escapeTapDesc(s) {
242
+ return s.replace(/[\r\n]+/g, ' ').replace(/#/g, '\\#');
243
+ }
244
+ function escapeTapMsg(s) {
245
+ return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/[\r\n]+/g, ' ');
246
+ }
247
+ function describeStep(step) {
248
+ if (!step || typeof step !== 'object')
249
+ return 'step';
250
+ const keys = Object.keys(step);
251
+ if (keys.length === 0)
252
+ return 'step';
253
+ const key = keys[0];
254
+ const val = step[key];
255
+ if (typeof val === 'string')
256
+ return `${key}: ${val}`;
257
+ if (typeof val === 'number' || typeof val === 'boolean')
258
+ return `${key}: ${String(val)}`;
259
+ return key;
260
+ }
21
261
  function renderJUnit(r) {
22
262
  const tests = r.flows.length;
23
263
  const failures = r.flows.filter((f) => !f.passed).length;
@@ -122,6 +362,29 @@ function renderHTML(r) {
122
362
  .visual-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin-top: 12px; }
123
363
  ul { margin: 8px 0; padding-left: 20px; font-size: 13px; }
124
364
  code { background: #F3F4F6; padding: 1px 6px; border-radius: 3px; font-size: 12px; }
365
+ details { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 12px 16px; margin-bottom: 12px; }
366
+ details[open] { padding-bottom: 16px; }
367
+ summary { cursor: pointer; font-weight: 600; display: flex; justify-content: space-between; align-items: center; gap: 12px; list-style: none; }
368
+ summary::-webkit-details-marker { display: none; }
369
+ summary::before { content: '\\25B8'; display: inline-block; transition: transform 0.15s ease; color: var(--muted); font-size: 10px; margin-right: 6px; }
370
+ details[open] > summary::before { transform: rotate(90deg); }
371
+ .summary-meta { display: flex; align-items: center; gap: 8px; font-weight: 400; color: var(--muted); font-size: 13px; }
372
+ table { width: 100%; border-collapse: collapse; font-size: 12px; margin-top: 8px; }
373
+ th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
374
+ th { color: var(--muted); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; background: #F9FAFB; }
375
+ tbody tr:last-child td { border-bottom: none; }
376
+ .pill-warn { background: #FFFBEB; color: #92400E; }
377
+ .pill-error { background: #FEF2F2; color: #991B1B; }
378
+ .pill-info { background: var(--blue-bg); color: var(--blue); }
379
+ .pill-high { background: #FEF2F2; color: #991B1B; }
380
+ .pill-medium { background: #FFFBEB; color: #92400E; }
381
+ .pill-low { background: var(--blue-bg); color: var(--blue); }
382
+ .kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-size: 13px; margin-top: 8px; }
383
+ .kv dt { color: var(--muted); }
384
+ .kv dd { margin: 0; color: var(--text); word-break: break-word; }
385
+ .empty { color: var(--muted); font-style: italic; font-size: 13px; }
386
+ .mono { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; }
387
+ .trunc { max-width: 420px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; vertical-align: middle; }
125
388
  </style>
126
389
  </head>
127
390
  <body>
@@ -145,10 +408,64 @@ function renderHTML(r) {
145
408
  ${r.links?.length ? `<h2>Broken links</h2>${r.links.map(renderLinks).join('')}` : ''}
146
409
  ${r.pwa?.length ? `<h2>PWA</h2>${r.pwa.map(renderPwa).join('')}` : ''}
147
410
  ${r.security ? `<h2>Security headers</h2>${renderSecurity(r.security)}` : ''}
411
+ ${r.retire?.length ? `<h2>Vulnerable libraries</h2>${r.retire.map(renderRetire).join('')}` : ''}
412
+ ${r.deadClicks?.length ? `<h2>Dead clicks</h2>${r.deadClicks.map(renderDeadClicks).join('')}` : ''}
413
+ ${r.touchTargets?.length ? `<h2>Touch targets</h2>${r.touchTargets.map(renderTouchTargets).join('')}` : ''}
414
+ ${r.keyboard?.length ? `<h2>Keyboard</h2>${r.keyboard.map(renderKeyboard).join('')}` : ''}
415
+ ${r.longTasks?.length ? `<h2>Long tasks & INP</h2>${r.longTasks.map(renderLongTasks).join('')}` : ''}
416
+ ${r.clsTimeline?.length ? `<h2>Layout shifts</h2>${r.clsTimeline.map(renderClsTimeline).join('')}` : ''}
417
+ ${r.forms?.length ? `<h2>Forms</h2>${r.forms.map(renderForms).join('')}` : ''}
418
+ ${r.structuredData?.length ? `<h2>Structured data</h2>${r.structuredData.map(renderStructuredData).join('')}` : ''}
419
+ ${r.passiveSecurity?.length ? `<h2>Passive security</h2>${r.passiveSecurity.map(renderPassiveSecurity).join('')}` : ''}
420
+ ${r.consoleErrors?.length ? `<h2>Console errors</h2>${r.consoleErrors.map(renderConsoleErrors).join('')}` : ''}
421
+ ${r.sitemap ? `<h2>Sitemap</h2>${renderSitemap(r.sitemap)}` : ''}
422
+ ${r.redirects ? `<h2>Redirects</h2>${renderRedirects(r.redirects)}` : ''}
423
+ ${r.exposedPaths ? `<h2>Exposed paths</h2>${renderExposedPaths(r.exposedPaths)}` : ''}
424
+ ${r.tls ? `<h2>TLS</h2>${renderTls(r.tls)}` : ''}
425
+ ${r.crawl ? `<h2>Crawl</h2>${renderCrawl(r.crawl)}` : ''}
426
+ ${r.contentQuality ? `<h2>Content quality</h2>${renderContentQuality(r.contentQuality)}` : ''}
427
+ ${r.resourceHints?.length ? `<h2>Resource hints</h2>${r.resourceHints.map(renderResourceHints).join('')}` : ''}
428
+ ${r.mixedContent?.length ? `<h2>Mixed content</h2>${r.mixedContent.map(renderMixedContent).join('')}` : ''}
429
+ ${r.compression ? `<h2>Compression</h2>${renderCompression(r.compression)}` : ''}
430
+ ${r.cacheHeaders?.length ? `<h2>Cache headers</h2>${r.cacheHeaders.map(renderCacheHeaders).join('')}` : ''}
431
+ ${r.cookieBanner?.length ? `<h2>Cookie banner</h2>${r.cookieBanner.map(renderCookieBanner).join('')}` : ''}
432
+ ${r.thirdParty?.length ? `<h2>Third-party resources</h2>${r.thirdParty.map(renderThirdParty).join('')}` : ''}
433
+ ${r.bundleSize?.length ? `<h2>Bundle size</h2>${r.bundleSize.map(renderBundleSize).join('')}` : ''}
434
+ ${r.openGraph?.length ? `<h2>Open Graph</h2>${r.openGraph.map(renderOpenGraph).join('')}` : ''}
435
+ ${r.robotsAudit ? `<h2>robots.txt</h2>${renderRobotsAudit(r.robotsAudit)}` : ''}
436
+ ${r.imageAudit?.length ? `<h2>Images</h2>${r.imageAudit.map(renderImageAudit).join('')}` : ''}
437
+ ${r.webfonts?.length ? `<h2>Webfonts</h2>${r.webfonts.map(renderWebfonts).join('')}` : ''}
438
+ ${r.motionPrefs?.length ? `<h2>Motion preferences</h2>${r.motionPrefs.map(renderMotionPrefs).join('')}` : ''}
148
439
  ${r.explore ? `<h2>Exploration</h2>${renderExplore(r.explore)}` : ''}
440
+ ${renderUnknownSections(r)}
149
441
  </body>
150
442
  </html>`;
151
443
  }
444
+ const KNOWN_RESULT_KEYS = new Set([
445
+ 'url', 'startedAt', 'finishedAt', 'durationMs', 'flows', 'budget',
446
+ 'a11y', 'perf', 'visual', 'seo', 'links', 'pwa', 'security', 'retire',
447
+ 'deadClicks', 'touchTargets', 'keyboard', 'longTasks', 'clsTimeline', 'forms',
448
+ 'structuredData', 'passiveSecurity', 'consoleErrors', 'sitemap', 'redirects',
449
+ 'exposedPaths', 'tls', 'crawl', 'contentQuality', 'resourceHints', 'mixedContent',
450
+ 'compression', 'cacheHeaders', 'cookieBanner', 'thirdParty', 'bundleSize',
451
+ 'openGraph', 'robotsAudit', 'imageAudit', 'webfonts', 'motionPrefs', 'explore',
452
+ 'apiFlows', 'passed',
453
+ ]);
454
+ function renderUnknownSections(r) {
455
+ const out = [];
456
+ for (const key of Object.keys(r)) {
457
+ if (KNOWN_RESULT_KEYS.has(key))
458
+ continue;
459
+ const val = r[key];
460
+ if (val === undefined || val === null)
461
+ continue;
462
+ if (Array.isArray(val) && val.length === 0)
463
+ continue;
464
+ const title = escape(key.replace(/([A-Z])/g, ' $1').replace(/^./, (c) => c.toUpperCase()));
465
+ out.push(`<h2>${title}</h2><div class="section"><pre>${escape(JSON.stringify(val, null, 2))}</pre></div>`);
466
+ }
467
+ return out.join('');
468
+ }
152
469
  function renderFlow(f) {
153
470
  return `<div class="section">
154
471
  <div class="row"><strong>${escape(f.name)}</strong> <span class="${f.passed ? 'pass' : 'fail'}">${f.passed ? 'PASS' : 'FAIL'}</span></div>
@@ -243,6 +560,424 @@ function renderExplore(e) {
243
560
  </div>`;
244
561
  }
245
562
  function escape(s) {
246
- return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
563
+ return (s ?? '').toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
564
+ }
565
+ function statusBadge(passed, label) {
566
+ return `<span class="${passed ? 'pass' : 'fail'}">${label ?? (passed ? 'PASS' : 'FAIL')}</span>`;
567
+ }
568
+ function summaryRow(title, passed, meta) {
569
+ return `<summary><span>${escape(title)}</span><span class="summary-meta">${meta} ${statusBadge(passed)}</span></summary>`;
570
+ }
571
+ function plural(n, one, many) {
572
+ return `${n} ${n === 1 ? one : many ?? one + 's'}`;
573
+ }
574
+ function bytes(n) {
575
+ if (n === undefined || n === null || !Number.isFinite(n))
576
+ return '—';
577
+ if (n < 1024)
578
+ return `${n} B`;
579
+ if (n < 1024 * 1024)
580
+ return `${(n / 1024).toFixed(1)} KB`;
581
+ return `${(n / 1024 / 1024).toFixed(2)} MB`;
582
+ }
583
+ function renderRetire(r) {
584
+ const pageUrl = r.findings[0]?.url ?? '';
585
+ const vulnCount = r.findings.reduce((n, f) => n + f.vulnerabilities.length, 0);
586
+ const meta = `${plural(r.findings.length, 'library', 'libraries')} · ${plural(vulnCount, 'vulnerability', 'vulnerabilities')} · ${r.librariesScanned} scanned`;
587
+ const body = r.findings.length === 0
588
+ ? `<div class="empty">No vulnerable libraries detected.</div>`
589
+ : `<table><thead><tr><th>Library</th><th>Version</th><th>Severity</th><th>Summary</th><th>CVE</th></tr></thead><tbody>${r.findings.map((f) => f.vulnerabilities.map((v) => `
590
+ <tr>
591
+ <td><strong>${escape(f.library)}</strong><div class="mono trunc">${escape(f.url)}</div></td>
592
+ <td><code>${escape(f.version)}</code></td>
593
+ <td><span class="pill pill-${v.severity === 'critical' ? 'critical' : v.severity === 'high' ? 'serious' : v.severity === 'medium' ? 'moderate' : 'minor'}">${escape(v.severity)}</span></td>
594
+ <td>${escape(v.summary)}</td>
595
+ <td class="mono">${(v.identifiers?.CVE ?? []).map((c) => escape(c)).join(', ') || '—'}</td>
596
+ </tr>`).join('')).join('')}</tbody></table>`;
597
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(pageUrl || 'Retire.js', r.passed, meta)}${body}</details>`;
598
+ }
599
+ function renderDeadClicks(r) {
600
+ const meta = `${plural(r.clicked, 'click')} · ${plural(r.findings.length, 'dead click')}`;
601
+ const body = r.findings.length === 0
602
+ ? `<div class="empty">All interactive elements responded.</div>`
603
+ : `<table><thead><tr><th>Selector</th><th>Reason</th><th>Feedback</th><th>HTML</th></tr></thead><tbody>${r.findings.map((f) => `
604
+ <tr>
605
+ <td class="mono trunc">${escape(f.selector)}</td>
606
+ <td><span class="pill pill-warn">${escape(f.reason)}</span></td>
607
+ <td>${f.feedbackMs !== undefined ? `${f.feedbackMs}ms` : '—'}</td>
608
+ <td class="mono trunc">${escape(f.html)}</td>
609
+ </tr>`).join('')}</tbody></table>`;
610
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
611
+ }
612
+ function renderTouchTargets(r) {
613
+ const meta = `${r.scanned} scanned · ${plural(r.tooSmall.length, 'too small')} · ${plural(r.overlapping.length, 'overlap')}`;
614
+ const rows = (arr, kind) => arr.map((f) => `
615
+ <tr>
616
+ <td class="mono trunc">${escape(f.selector)}</td>
617
+ <td><span class="pill pill-warn">${kind}</span></td>
618
+ <td>${Math.round(f.width)}×${Math.round(f.height)}px</td>
619
+ <td class="mono trunc">${escape(f.overlapsWith ?? '')}</td>
620
+ </tr>`).join('');
621
+ const body = r.tooSmall.length === 0 && r.overlapping.length === 0
622
+ ? `<div class="empty">All touch targets meet minimum size.</div>`
623
+ : `<table><thead><tr><th>Selector</th><th>Issue</th><th>Size</th><th>Overlaps with</th></tr></thead><tbody>${rows(r.tooSmall, 'too-small')}${rows(r.overlapping, 'overlapping')}</tbody></table>`;
624
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
625
+ }
626
+ function renderKeyboard(r) {
627
+ const meta = `${r.focusableCount} focusable · ${r.tabsTaken} tabs · ${plural(r.issues.length, 'issue')}`;
628
+ const body = r.issues.length === 0
629
+ ? `<div class="empty">Keyboard navigation is healthy.</div>`
630
+ : `<table><thead><tr><th>Level</th><th>Type</th><th>Selector</th><th>Message</th></tr></thead><tbody>${r.issues.map((i) => `
631
+ <tr>
632
+ <td><span class="pill pill-${i.level === 'error' ? 'error' : 'warn'}">${escape(i.level)}</span></td>
633
+ <td><code>${escape(i.type)}</code></td>
634
+ <td class="mono trunc">${escape(i.selector ?? '—')}</td>
635
+ <td>${escape(i.message)}</td>
636
+ </tr>`).join('')}</tbody></table>`;
637
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
638
+ }
639
+ function renderLongTasks(r) {
640
+ const meta = `TBT ${Math.round(r.totalBlockingMs)}ms · INP ${r.inpMs !== undefined ? `${Math.round(r.inpMs)}ms` : '—'} · ${plural(r.longTasks.length, 'long task')}`;
641
+ const body = `
642
+ <dl class="kv">
643
+ <dt>Total blocking</dt><dd>${Math.round(r.totalBlockingMs)}ms</dd>
644
+ <dt>INP</dt><dd>${r.inpMs !== undefined ? `${Math.round(r.inpMs)}ms${r.inpTarget ? ` on <code>${escape(r.inpTarget)}</code>` : ''}` : '—'}</dd>
645
+ <dt>Long tasks</dt><dd>${r.longTasks.length}</dd>
646
+ <dt>LoAF frames</dt><dd>${r.longAnimationFrames.length}</dd>
647
+ </dl>
648
+ ${r.longTasks.length ? `<table><thead><tr><th>Start</th><th>Duration</th><th>Attribution</th></tr></thead><tbody>${r.longTasks.slice(0, 20).map((t) => `
649
+ <tr>
650
+ <td>${Math.round(t.startTime)}ms</td>
651
+ <td>${Math.round(t.duration)}ms</td>
652
+ <td class="mono trunc">${(t.attribution ?? []).map((a) => escape([a.containerType, a.containerName, a.containerSrc].filter(Boolean).join(' '))).join(', ') || '—'}</td>
653
+ </tr>`).join('')}</tbody></table>` : ''}`;
654
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
655
+ }
656
+ function renderClsTimeline(r) {
657
+ const meta = `CLS ${r.cls.toFixed(3)} · ${plural(r.timeline.length, 'shift')}`;
658
+ const body = r.worstElements.length === 0
659
+ ? `<div class="empty">No layout shifts recorded.</div>`
660
+ : `<table><thead><tr><th>Selector</th><th>Total shift</th><th>Occurrences</th></tr></thead><tbody>${r.worstElements.map((e) => `
661
+ <tr>
662
+ <td class="mono trunc">${escape(e.selector)}</td>
663
+ <td>${e.totalShift.toFixed(4)}</td>
664
+ <td>${e.occurrences}</td>
665
+ </tr>`).join('')}</tbody></table>`;
666
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
667
+ }
668
+ function renderForms(r) {
669
+ const meta = `${plural(r.forms.length, 'form')} · ${plural(r.totalIssues, 'issue')}`;
670
+ const body = r.forms.length === 0
671
+ ? `<div class="empty">No forms on this page.</div>`
672
+ : r.forms.map((f) => `
673
+ <div style="margin-top:12px">
674
+ <div><strong class="mono">${escape(f.selector)}</strong> <span class="label">${escape(f.method)} · ${f.fields} fields</span></div>
675
+ ${f.issues.length ? `<table><thead><tr><th>Level</th><th>Type</th><th>Selector</th><th>Message</th></tr></thead><tbody>${f.issues.map((i) => `
676
+ <tr>
677
+ <td><span class="pill pill-${i.level === 'error' ? 'error' : 'warn'}">${escape(i.level)}</span></td>
678
+ <td><code>${escape(i.type)}</code></td>
679
+ <td class="mono trunc">${escape(i.selector)}</td>
680
+ <td>${escape(i.message)}</td>
681
+ </tr>`).join('')}</tbody></table>` : '<div class="empty">No issues.</div>'}
682
+ </div>`).join('');
683
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
684
+ }
685
+ function renderStructuredData(r) {
686
+ const meta = `${plural(r.items.length, 'item')} · ${plural(r.hreflangTags.length, 'hreflang')} · ${plural(r.issues.length, 'issue')}`;
687
+ const items = r.items.length
688
+ ? `<table><thead><tr><th>Format</th><th>Type</th></tr></thead><tbody>${r.items.map((i) => `<tr><td><code>${escape(i.format)}</code></td><td>${escape(i.type)}</td></tr>`).join('')}</tbody></table>` : '';
689
+ const issues = r.issues.length
690
+ ? `<table><thead><tr><th>Level</th><th>Type</th><th>Message</th></tr></thead><tbody>${r.issues.map((i) => `
691
+ <tr>
692
+ <td><span class="pill pill-${i.level === 'error' ? 'error' : 'warn'}">${escape(i.level)}</span></td>
693
+ <td><code>${escape(i.type)}</code></td>
694
+ <td>${escape(i.message)}${i.snippet ? `<div class="mono trunc">${escape(i.snippet)}</div>` : ''}</td>
695
+ </tr>`).join('')}</tbody></table>` : '';
696
+ const body = r.items.length === 0 && r.issues.length === 0
697
+ ? `<div class="empty">No structured data found.</div>`
698
+ : items + issues;
699
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
700
+ }
701
+ function renderPassiveSecurity(r) {
702
+ const meta = `${plural(r.issues.length, 'issue')} · ${r.scannedScripts} scripts · ${r.scannedLinks} links · ${r.cookiesChecked} cookies`;
703
+ const body = r.issues.length === 0
704
+ ? `<div class="empty">No passive security smells.</div>`
705
+ : `<table><thead><tr><th>Level</th><th>Type</th><th>Target</th><th>Message</th></tr></thead><tbody>${r.issues.map((i) => `
706
+ <tr>
707
+ <td><span class="pill pill-${i.level === 'error' ? 'error' : 'warn'}">${escape(i.level)}</span></td>
708
+ <td><code>${escape(i.type)}</code></td>
709
+ <td class="mono trunc">${escape(i.selector ?? i.url ?? i.cookieName ?? '—')}</td>
710
+ <td>${escape(i.message)}</td>
711
+ </tr>`).join('')}</tbody></table>`;
712
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
713
+ }
714
+ function renderConsoleErrors(r) {
715
+ const meta = `${r.errorCount} errors · ${r.warningCount} warnings · ${plural(r.issues.length, 'unique issue')}`;
716
+ const body = r.issues.length === 0
717
+ ? `<div class="empty">No console messages captured.</div>`
718
+ : `<table><thead><tr><th>Type</th><th>Count</th><th>Message</th><th>Source</th></tr></thead><tbody>${r.issues.map((i) => `
719
+ <tr>
720
+ <td><span class="pill pill-${i.type === 'warning' ? 'warn' : 'error'}">${escape(i.type)}</span></td>
721
+ <td>${i.occurrences}</td>
722
+ <td class="mono trunc">${escape(i.message)}</td>
723
+ <td class="mono trunc">${escape([i.url, i.lineNumber, i.columnNumber].filter((v) => v !== undefined).join(':'))}</td>
724
+ </tr>`).join('')}</tbody></table>`;
725
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
726
+ }
727
+ function renderSitemap(r) {
728
+ const meta = `${r.sitemapFound ? 'found' : 'missing'} · ${r.urlsInSitemap} urls · ${plural(r.brokenUrls.length, 'broken')} · ${plural(r.issues.length, 'issue')}`;
729
+ const body = `
730
+ <dl class="kv">
731
+ <dt>Sitemap URL</dt><dd>${r.sitemapUrl ? `<code>${escape(r.sitemapUrl)}</code>` : '—'}</dd>
732
+ <dt>robots.txt</dt><dd>${r.robotsTxtFound ? 'found' : 'missing'}</dd>
733
+ <dt>URLs checked</dt><dd>${r.urlsChecked}</dd>
734
+ <dt>Blocked critical</dt><dd>${r.robotsBlockedCritical.length ? r.robotsBlockedCritical.map((u) => `<code>${escape(u)}</code>`).join(' ') : '—'}</dd>
735
+ </dl>
736
+ ${r.brokenUrls.length ? `<table><thead><tr><th>Status</th><th>URL</th></tr></thead><tbody>${r.brokenUrls.map((b) => `<tr><td><code>${b.status}</code></td><td class="mono trunc">${escape(b.url)}</td></tr>`).join('')}</tbody></table>` : ''}
737
+ ${r.issues.length ? `<ul>${r.issues.map((i) => `<li><span class="pill pill-${i.level === 'error' ? 'error' : i.level === 'warn' ? 'warn' : 'info'}">${escape(i.level)}</span> ${escape(i.message)}${i.url ? ` — <code>${escape(i.url)}</code>` : ''}</li>`).join('')}</ul>` : ''}`;
738
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.baseUrl, r.passed, meta)}${body}</details>`;
739
+ }
740
+ function renderRedirects(r) {
741
+ const meta = `${r.hopCount} hops${r.loop ? ' · loop' : ''}${r.mixedScheme ? ' · mixed scheme' : ''}${r.metaRefresh ? ' · meta-refresh' : ''}`;
742
+ const body = `
743
+ <dl class="kv">
744
+ <dt>Start</dt><dd class="mono trunc">${escape(r.start)}</dd>
745
+ <dt>Final</dt><dd class="mono trunc">${escape(r.final)}</dd>
746
+ </dl>
747
+ ${r.hops.length ? `<table><thead><tr><th>#</th><th>Status</th><th>Method</th><th>URL</th><th>Location</th><th>Time</th></tr></thead><tbody>${r.hops.map((h, idx) => `
748
+ <tr>
749
+ <td>${idx + 1}</td>
750
+ <td><code>${h.status}</code></td>
751
+ <td>${escape(h.method)}</td>
752
+ <td class="mono trunc">${escape(h.url)}</td>
753
+ <td class="mono trunc">${escape(h.location ?? '—')}</td>
754
+ <td>${Math.round(h.durationMs)}ms</td>
755
+ </tr>`).join('')}</tbody></table>` : ''}`;
756
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.start, r.passed, meta)}${body}</details>`;
757
+ }
758
+ function renderExposedPaths(r) {
759
+ const meta = `${r.scanned} scanned · ${plural(r.findings.length, 'finding')}${r.securityTxtPresent ? ' · security.txt' : ''}`;
760
+ const body = r.findings.length === 0
761
+ ? `<div class="empty">No exposed paths detected.</div>`
762
+ : `<table><thead><tr><th>Severity</th><th>Path</th><th>Status</th><th>Snippet</th></tr></thead><tbody>${r.findings.map((f) => `
763
+ <tr>
764
+ <td><span class="pill pill-${f.severity}">${escape(f.severity)}</span></td>
765
+ <td class="mono trunc">${escape(f.path)}</td>
766
+ <td><code>${f.status}</code></td>
767
+ <td class="mono trunc">${escape(f.contentSnippet ?? '')}</td>
768
+ </tr>`).join('')}</tbody></table>`;
769
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.baseUrl, r.passed, meta)}${body}</details>`;
770
+ }
771
+ function renderTls(r) {
772
+ const meta = `${r.protocol ?? '—'}${r.cert ? ` · expires in ${r.cert.daysUntilExpiry}d` : ''} · ${plural(r.issues.length, 'issue')}`;
773
+ const body = `
774
+ <dl class="kv">
775
+ <dt>Host</dt><dd>${escape(r.host)}:${r.port}</dd>
776
+ <dt>Protocol</dt><dd>${escape(r.protocol ?? '—')}</dd>
777
+ <dt>Cipher</dt><dd>${r.cipher ? `${escape(r.cipher.name)} (${escape(r.cipher.version)})` : '—'}</dd>
778
+ ${r.cert ? `
779
+ <dt>Subject</dt><dd class="mono trunc">${escape(r.cert.subject)}</dd>
780
+ <dt>Issuer</dt><dd class="mono trunc">${escape(r.cert.issuer)}</dd>
781
+ <dt>Valid</dt><dd>${escape(r.cert.validFrom)} → ${escape(r.cert.validTo)} (${r.cert.daysUntilExpiry}d)</dd>
782
+ <dt>Key length</dt><dd>${r.cert.keyLength ?? '—'}${r.cert.selfSigned ? ' · self-signed' : ''}</dd>
783
+ ` : ''}
784
+ <dt>Chain</dt><dd>${r.chainComplete ? 'complete' : 'incomplete'}</dd>
785
+ <dt>HSTS</dt><dd class="mono trunc">${escape(r.hstsHeader ?? '—')}${r.hstsPreloadEligible ? ' · preload-eligible' : ''}</dd>
786
+ </dl>
787
+ ${r.issues.length ? `<ul>${r.issues.map((i) => `<li><span class="pill pill-${i.level === 'error' ? 'error' : i.level === 'warn' ? 'warn' : 'info'}">${escape(i.level)}</span> ${escape(i.message)}</li>`).join('')}</ul>` : ''}`;
788
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.host, r.passed, meta)}${body}</details>`;
789
+ }
790
+ function renderCrawl(r) {
791
+ const errors = r.pages.filter((p) => p.error || (p.status >= 400 && p.status !== 0)).length;
792
+ const passed = errors === 0;
793
+ const meta = `${r.pagesVisited} pages · ${plural(errors, 'error')} · ${(r.durationMs / 1000).toFixed(1)}s`;
794
+ const body = `
795
+ <dl class="kv">
796
+ <dt>Seed</dt><dd class="mono trunc">${escape(r.seed)}</dd>
797
+ <dt>Pages visited</dt><dd>${r.pagesVisited}</dd>
798
+ <dt>Duration</dt><dd>${(r.durationMs / 1000).toFixed(1)}s</dd>
799
+ </dl>
800
+ <table><thead><tr><th>Depth</th><th>Status</th><th>URL</th><th>Title</th><th>Load</th></tr></thead><tbody>${r.pages.slice(0, 100).map((p) => `
801
+ <tr>
802
+ <td>${p.depth}</td>
803
+ <td><code class="${p.status >= 400 || p.error ? 'fail' : 'pass'}">${p.error ? 'ERR' : p.status}</code></td>
804
+ <td class="mono trunc">${escape(p.url)}</td>
805
+ <td class="trunc">${escape(p.title ?? '')}</td>
806
+ <td>${Math.round(p.loadTimeMs)}ms</td>
807
+ </tr>`).join('')}</tbody></table>`;
808
+ return `<details${passed ? '' : ' open'}>${summaryRow(r.seed, passed, meta)}${body}</details>`;
809
+ }
810
+ function renderContentQuality(r) {
811
+ const meta = `${r.pages.length} pages · ${plural(r.thinContent.length, 'thin')} · ${plural(r.duplicates.length, 'duplicate')} · ${plural(r.issues.length, 'issue')}`;
812
+ const body = `
813
+ <table><thead><tr><th>URL</th><th>Words</th><th>H1s</th><th>Flesch</th><th>Grade</th></tr></thead><tbody>${r.pages.slice(0, 50).map((p) => `
814
+ <tr>
815
+ <td class="mono trunc">${escape(p.url)}</td>
816
+ <td>${p.wordCount}</td>
817
+ <td class="${p.h1Count === 1 ? 'pass' : 'fail'}">${p.h1Count}</td>
818
+ <td>${p.fleschReadingEase}</td>
819
+ <td>${p.fleschKincaidGrade}</td>
820
+ </tr>`).join('')}</tbody></table>
821
+ ${r.duplicates.length ? `<h3 style="font-size:13px;margin-top:12px">Duplicates</h3><ul>${r.duplicates.map((d) => `<li><code>${escape(d.kind)}</code> (${d.similarity.toFixed(2)}): ${d.urls.map((u) => `<span class="mono">${escape(u)}</span>`).join(', ')}</li>`).join('')}</ul>` : ''}
822
+ ${r.issues.length ? `<ul>${r.issues.map((i) => `<li><span class="pill pill-${i.level === 'error' ? 'error' : 'warn'}">${escape(i.level)}</span> ${escape(i.message)}${i.url ? ` — <code>${escape(i.url)}</code>` : ''}</li>`).join('')}</ul>` : ''}`;
823
+ return `<details${r.passed ? '' : ' open'}>${summaryRow('Content quality', r.passed, meta)}${body}</details>`;
824
+ }
825
+ function renderIssueTable(issues, targetKey = 'target') {
826
+ if (!issues?.length)
827
+ return '<div class="empty">No issues.</div>';
828
+ return `<table><thead><tr><th>Type</th><th>Target</th><th>Detail</th></tr></thead><tbody>${issues.map((i) => `
829
+ <tr>
830
+ <td><code>${escape(i.type)}</code></td>
831
+ <td class="mono trunc">${escape(i[targetKey] ?? '')}</td>
832
+ <td>${escape(i.detail ?? i.message ?? '')}</td>
833
+ </tr>`).join('')}</tbody></table>`;
834
+ }
835
+ function renderResourceHints(r) {
836
+ const meta = `${plural(r.hints.length, 'hint')} · score ${r.score} · ${plural(r.issues.length, 'issue')}`;
837
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${renderIssueTable(r.issues)}</details>`;
838
+ }
839
+ function renderMixedContent(r) {
840
+ const meta = `${r.httpsPage ? 'HTTPS' : 'HTTP'} · ${plural(r.insecureResources.length, 'insecure resource')}`;
841
+ const body = `
842
+ <dl class="kv">
843
+ <dt>CSP present</dt><dd>${r.cspPresent ? 'yes' : 'no'}</dd>
844
+ <dt>upgrade-insecure-requests</dt><dd>${r.cspUpgradeInsecure ? 'yes' : 'no'}</dd>
845
+ <dt>block-all-mixed-content</dt><dd>${r.cspBlockAllMixed ? 'yes' : 'no'}</dd>
846
+ <dt>Referrer-Policy</dt><dd>${escape(r.referrerPolicy ?? '—')}</dd>
847
+ </dl>
848
+ ${r.insecureResources.length ? `<table><thead><tr><th>Type</th><th>URL</th></tr></thead><tbody>${r.insecureResources.map((i) => `<tr><td>${escape(i.type ?? i.tag ?? '')}</td><td class="mono trunc">${escape(i.url)}</td></tr>`).join('')}</tbody></table>` : ''}`;
849
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
850
+ }
851
+ function renderCompression(r) {
852
+ const meta = `${escape(r.contentEncoding ?? 'identity')} · ${escape(r.httpVersion ?? '—')} · ${plural(r.issues.length, 'issue')}`;
853
+ const body = `
854
+ <dl class="kv">
855
+ <dt>HTTP version</dt><dd>${escape(r.httpVersion ?? '—')}</dd>
856
+ <dt>Content-Encoding</dt><dd>${escape(r.contentEncoding ?? '—')}</dd>
857
+ <dt>Content-Length</dt><dd>${bytes(r.contentLength)}</dd>
858
+ <dt>Transfer size</dt><dd>${bytes(r.transferLength)}</dd>
859
+ <dt>Ratio</dt><dd>${r.compressionRatio !== undefined ? r.compressionRatio.toFixed(2) : '—'}</dd>
860
+ <dt>Brotli supported</dt><dd>${r.supportsBrotli ? 'yes' : 'no'}</dd>
861
+ <dt>Alt-Svc has h3</dt><dd>${r.altSvcHasH3 ? 'yes' : 'no'}</dd>
862
+ </dl>
863
+ ${r.issues.length ? `<ul>${r.issues.map((i) => `<li>${escape(i)}</li>`).join('')}</ul>` : ''}`;
864
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.url, r.passed, meta)}${body}</details>`;
865
+ }
866
+ function renderCacheHeaders(r) {
867
+ const meta = `${r.resources.length} resources · ${plural(r.issues.length, 'issue')}`;
868
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${renderIssueTable(r.issues)}</details>`;
869
+ }
870
+ function renderCookieBanner(r) {
871
+ const meta = `${r.bannerDetected ? 'banner detected' : 'no banner'} · ${plural(r.issues.length, 'issue')}`;
872
+ const body = `
873
+ <dl class="kv">
874
+ <dt>Accept button</dt><dd>${r.hasAcceptButton ? 'yes' : 'no'}</dd>
875
+ <dt>Reject button</dt><dd>${r.hasRejectButton ? 'yes' : 'no'}</dd>
876
+ <dt>Settings button</dt><dd>${r.hasSettingsButton ? 'yes' : 'no'}</dd>
877
+ <dt>Cookies before consent</dt><dd>${r.beforeConsentCookies.length}</dd>
878
+ <dt>Trackers before consent</dt><dd>${r.beforeConsentTrackers.length ? r.beforeConsentTrackers.map((t) => `<code>${escape(t)}</code>`).join(' ') : '—'}</dd>
879
+ </dl>
880
+ ${r.issues.length ? `<ul>${r.issues.map((i) => `<li><code>${escape(i.type)}</code> — ${escape(i.detail)}</li>`).join('')}</ul>` : ''}`;
881
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
882
+ }
883
+ function renderThirdParty(r) {
884
+ const meta = `${r.thirdPartyResources}/${r.totalResources} resources · ${bytes(r.thirdPartyBytes)} · ${Math.round(r.thirdPartyBlockingMs)}ms blocking`;
885
+ const body = `
886
+ <dl class="kv">
887
+ <dt>First-party origin</dt><dd class="mono trunc">${escape(r.firstPartyOrigin)}</dd>
888
+ <dt>Third-party bytes</dt><dd>${bytes(r.thirdPartyBytes)}</dd>
889
+ <dt>Blocking time</dt><dd>${Math.round(r.thirdPartyBlockingMs)}ms</dd>
890
+ </dl>
891
+ ${r.topEntities?.length ? `<table><thead><tr><th>Entity</th><th>Requests</th><th>Bytes</th></tr></thead><tbody>${r.topEntities.slice(0, 20).map((e) => `<tr><td>${escape(e.name ?? e.entity ?? '—')}</td><td>${e.requests ?? e.count ?? '—'}</td><td>${bytes(e.bytes)}</td></tr>`).join('')}</tbody></table>` : ''}
892
+ ${r.issues?.length ? renderIssueTable(r.issues) : ''}`;
893
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
894
+ }
895
+ function renderBundleSize(r) {
896
+ const meta = `JS ${bytes(r.totalJsBytes)} · CSS ${bytes(r.totalCssBytes)} · ${plural(r.bundles.length, 'bundle')}`;
897
+ const body = `
898
+ <dl class="kv">
899
+ <dt>JS</dt><dd>${bytes(r.totalJsBytes)} (transfer ${bytes(r.totalJsTransferBytes)})</dd>
900
+ <dt>CSS</dt><dd>${bytes(r.totalCssBytes)} (transfer ${bytes(r.totalCssTransferBytes)})</dd>
901
+ <dt>Duplicate packages</dt><dd>${r.duplicatePackages?.length ?? 0}</dd>
902
+ </dl>
903
+ ${r.bundles?.length ? `<table><thead><tr><th>Type</th><th>URL</th><th>Size</th><th>Transfer</th><th>Framework</th></tr></thead><tbody>${r.bundles.slice(0, 30).map((b) => `
904
+ <tr>
905
+ <td><code>${escape(b.type)}</code></td>
906
+ <td class="mono trunc">${escape(b.url)}</td>
907
+ <td>${bytes(b.bytes)}</td>
908
+ <td>${bytes(b.transferBytes)}</td>
909
+ <td>${escape(b.framework ?? '—')}</td>
910
+ </tr>`).join('')}</tbody></table>` : ''}
911
+ ${r.issues?.length ? renderIssueTable(r.issues) : ''}`;
912
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
913
+ }
914
+ function renderOpenGraph(r) {
915
+ const og = r.openGraph ?? {};
916
+ const tw = r.twitter ?? {};
917
+ const meta = `og:${og.type ?? '—'} · twitter:${tw.card ?? '—'} · ${plural(r.issues.length, 'issue')}`;
918
+ const body = `
919
+ <dl class="kv">
920
+ <dt>og:title</dt><dd>${escape(og.title ?? '—')}</dd>
921
+ <dt>og:description</dt><dd>${escape(og.description ?? '—')}</dd>
922
+ <dt>og:image</dt><dd class="mono trunc">${escape(og.image ?? '—')}</dd>
923
+ <dt>og:url</dt><dd class="mono trunc">${escape(og.url ?? '—')}</dd>
924
+ <dt>image reachable</dt><dd>${r.imageReachable ? 'yes' : 'no'}</dd>
925
+ <dt>image size</dt><dd>${r.imageActualWidth ?? '—'}×${r.imageActualHeight ?? '—'}</dd>
926
+ <dt>twitter:card</dt><dd>${escape(tw.card ?? '—')}</dd>
927
+ <dt>twitter:site</dt><dd>${escape(tw.site ?? '—')}</dd>
928
+ </dl>
929
+ ${r.issues?.length ? `<ul>${r.issues.map((i) => `<li><code>${escape(i.type)}</code>${i.detail ? ` — ${escape(i.detail)}` : ''}</li>`).join('')}</ul>` : ''}`;
930
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
931
+ }
932
+ function renderRobotsAudit(r) {
933
+ const meta = `${r.present ? 'present' : 'missing'}${r.status !== undefined ? ` · ${r.status}` : ''} · ${r.disallowRules?.length ?? 0} disallow · ${plural(r.issues.length, 'issue')}`;
934
+ const body = `
935
+ <dl class="kv">
936
+ <dt>URL</dt><dd class="mono trunc">${escape(r.url)}</dd>
937
+ <dt>Size</dt><dd>${bytes(r.size)}</dd>
938
+ <dt>User agents</dt><dd>${(r.userAgents ?? []).map((u) => `<code>${escape(u)}</code>`).join(' ') || '—'}</dd>
939
+ <dt>Sitemap URLs</dt><dd>${(r.sitemapUrls ?? []).map((u) => `<div class="mono trunc">${escape(u)}</div>`).join('') || '—'}</dd>
940
+ <dt>Crawl-delay</dt><dd>${r.crawlDelay ?? '—'}</dd>
941
+ <dt>Wildcard disallow</dt><dd>${r.hasWildcardDisallow ? 'yes' : 'no'}</dd>
942
+ </dl>
943
+ ${r.issues?.length ? `<ul>${r.issues.map((i) => `<li><code>${escape(i.type)}</code> — ${escape(i.detail)}</li>`).join('')}</ul>` : ''}`;
944
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.url, r.passed, meta)}${body}</details>`;
945
+ }
946
+ function renderImageAudit(r) {
947
+ const meta = `${r.images?.length ?? 0} images · ${plural(r.issues.length, 'issue')}`;
948
+ const body = `
949
+ ${r.stats ? `<dl class="kv">${Object.entries(r.stats).map(([k, v]) => `<dt>${escape(k)}</dt><dd>${typeof v === 'number' ? (k.toLowerCase().includes('byte') ? bytes(v) : v) : escape(String(v))}</dd>`).join('')}</dl>` : ''}
950
+ ${r.issues?.length ? renderIssueTable(r.issues) : '<div class="empty">No issues.</div>'}`;
951
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
952
+ }
953
+ function renderWebfonts(r) {
954
+ const meta = `${r.totalFontsLoaded ?? r.fonts?.length ?? 0} fonts · ${bytes(r.totalFontBytes)} · ${plural(r.issues.length, 'issue')}`;
955
+ const body = `
956
+ ${r.fonts?.length ? `<table><thead><tr><th>Family</th><th>Source</th><th>Format</th><th>Size</th><th>Display</th><th>Preloaded</th></tr></thead><tbody>${r.fonts.slice(0, 30).map((f) => `
957
+ <tr>
958
+ <td>${escape(f.family)}</td>
959
+ <td><code>${escape(f.source)}</code></td>
960
+ <td>${escape(f.format ?? '—')}</td>
961
+ <td>${bytes(f.size)}</td>
962
+ <td>${escape(f.fontDisplay ?? '—')}</td>
963
+ <td>${f.preloaded ? 'yes' : 'no'}</td>
964
+ </tr>`).join('')}</tbody></table>` : ''}
965
+ ${r.issues?.length ? renderIssueTable(r.issues) : ''}`;
966
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
967
+ }
968
+ function renderMotionPrefs(r) {
969
+ const meta = `${r.animationsCount ?? 0} animations · ${r.autoplayVideos ?? 0} autoplay · ${plural(r.issues.length, 'issue')}`;
970
+ const body = `
971
+ <dl class="kv">
972
+ <dt>Respects reduced motion</dt><dd>${r.respectsReducedMotion ? 'yes' : 'no'}</dd>
973
+ <dt>Respects dark mode</dt><dd>${r.respectsDarkMode ? 'yes' : 'no'}</dd>
974
+ <dt>Respects print</dt><dd>${r.respectsPrint ? 'yes' : 'no'}</dd>
975
+ <dt>Respects forced colors</dt><dd>${r.respectsForcedColors ? 'yes' : 'no'}</dd>
976
+ <dt>Animations</dt><dd>${r.animationsCount}</dd>
977
+ <dt>Autoplay videos</dt><dd>${r.autoplayVideos}</dd>
978
+ <dt>Infinite animations</dt><dd>${r.infiniteAnimations}</dd>
979
+ </dl>
980
+ ${r.issues?.length ? renderIssueTable(r.issues) : ''}`;
981
+ return `<details${r.passed ? '' : ' open'}>${summaryRow(r.page, r.passed, meta)}${body}</details>`;
247
982
  }
248
983
  //# sourceMappingURL=report.js.map