@empline/preflight 1.1.11 → 1.1.13

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 (709) hide show
  1. package/dist/checks/consolidated/auth-storage-state.d.ts +3 -0
  2. package/dist/checks/consolidated/auth-storage-state.d.ts.map +1 -0
  3. package/dist/checks/consolidated/auth-storage-state.js +146 -0
  4. package/dist/checks/consolidated/auth-storage-state.js.map +1 -0
  5. package/dist/checks/consolidated/business.d.ts +50 -0
  6. package/dist/checks/consolidated/business.d.ts.map +1 -0
  7. package/dist/checks/consolidated/business.js +252 -0
  8. package/dist/checks/consolidated/business.js.map +1 -0
  9. package/dist/checks/consolidated/caching-strategy.d.ts +104 -0
  10. package/dist/checks/consolidated/caching-strategy.d.ts.map +1 -0
  11. package/dist/checks/consolidated/caching-strategy.js +725 -0
  12. package/dist/checks/consolidated/caching-strategy.js.map +1 -0
  13. package/dist/checks/consolidated/code-quality.d.ts +83 -0
  14. package/dist/checks/consolidated/code-quality.d.ts.map +1 -0
  15. package/dist/checks/consolidated/code-quality.js +445 -0
  16. package/dist/checks/consolidated/code-quality.js.map +1 -0
  17. package/dist/checks/consolidated/console-statements.d.ts +32 -0
  18. package/dist/checks/consolidated/console-statements.d.ts.map +1 -0
  19. package/dist/checks/consolidated/console-statements.js +304 -0
  20. package/dist/checks/consolidated/console-statements.js.map +1 -0
  21. package/dist/checks/consolidated/css-advanced-validation.d.ts +24 -0
  22. package/dist/checks/consolidated/css-advanced-validation.d.ts.map +1 -0
  23. package/dist/checks/consolidated/css-advanced-validation.js +415 -0
  24. package/dist/checks/consolidated/css-advanced-validation.js.map +1 -0
  25. package/dist/checks/consolidated/css-organization.d.ts +14 -0
  26. package/dist/checks/consolidated/css-organization.d.ts.map +1 -0
  27. package/dist/checks/consolidated/css-organization.js +432 -0
  28. package/dist/checks/consolidated/css-organization.js.map +1 -0
  29. package/dist/checks/consolidated/css-runtime-validation.d.ts +22 -0
  30. package/dist/checks/consolidated/css-runtime-validation.d.ts.map +1 -0
  31. package/dist/checks/consolidated/css-runtime-validation.js +330 -0
  32. package/dist/checks/consolidated/css-runtime-validation.js.map +1 -0
  33. package/dist/checks/consolidated/css-variable-validation.d.ts +17 -0
  34. package/dist/checks/consolidated/css-variable-validation.d.ts.map +1 -0
  35. package/dist/checks/consolidated/css-variable-validation.js +412 -0
  36. package/dist/checks/consolidated/css-variable-validation.js.map +1 -0
  37. package/dist/checks/consolidated/dark-mode-consistency.d.ts +23 -0
  38. package/dist/checks/consolidated/dark-mode-consistency.d.ts.map +1 -0
  39. package/dist/checks/consolidated/dark-mode-consistency.js +291 -0
  40. package/dist/checks/consolidated/dark-mode-consistency.js.map +1 -0
  41. package/dist/checks/consolidated/database.d.ts +95 -0
  42. package/dist/checks/consolidated/database.d.ts.map +1 -0
  43. package/dist/checks/consolidated/database.js +427 -0
  44. package/dist/checks/consolidated/database.js.map +1 -0
  45. package/dist/checks/consolidated/e2e-checks.d.ts +52 -0
  46. package/dist/checks/consolidated/e2e-checks.d.ts.map +1 -0
  47. package/dist/checks/consolidated/e2e-checks.js +157 -0
  48. package/dist/checks/consolidated/e2e-checks.js.map +1 -0
  49. package/dist/checks/consolidated/e2e-regression-coverage.d.ts +14 -0
  50. package/dist/checks/consolidated/e2e-regression-coverage.d.ts.map +1 -0
  51. package/dist/checks/consolidated/e2e-regression-coverage.js +151 -0
  52. package/dist/checks/consolidated/e2e-regression-coverage.js.map +1 -0
  53. package/dist/checks/consolidated/e2e-validation.d.ts +137 -0
  54. package/dist/checks/consolidated/e2e-validation.d.ts.map +1 -0
  55. package/dist/checks/consolidated/e2e-validation.js +1001 -0
  56. package/dist/checks/consolidated/e2e-validation.js.map +1 -0
  57. package/dist/checks/consolidated/enterprise-baseline.d.ts +9 -0
  58. package/dist/checks/consolidated/enterprise-baseline.d.ts.map +1 -0
  59. package/dist/checks/consolidated/enterprise-baseline.js +277 -0
  60. package/dist/checks/consolidated/enterprise-baseline.js.map +1 -0
  61. package/dist/checks/consolidated/generate-pageload-config.d.ts +6 -0
  62. package/dist/checks/consolidated/generate-pageload-config.d.ts.map +1 -0
  63. package/dist/checks/consolidated/generate-pageload-config.js +161 -0
  64. package/dist/checks/consolidated/generate-pageload-config.js.map +1 -0
  65. package/dist/checks/consolidated/hardened-checks.d.ts +276 -0
  66. package/dist/checks/consolidated/hardened-checks.d.ts.map +1 -0
  67. package/dist/checks/consolidated/hardened-checks.js +3056 -0
  68. package/dist/checks/consolidated/hardened-checks.js.map +1 -0
  69. package/dist/checks/consolidated/homepage-ux.d.ts +12 -0
  70. package/dist/checks/consolidated/homepage-ux.d.ts.map +1 -0
  71. package/dist/checks/consolidated/homepage-ux.js +242 -0
  72. package/dist/checks/consolidated/homepage-ux.js.map +1 -0
  73. package/dist/checks/consolidated/images.d.ts +76 -0
  74. package/dist/checks/consolidated/images.d.ts.map +1 -0
  75. package/dist/checks/consolidated/images.js +311 -0
  76. package/dist/checks/consolidated/images.js.map +1 -0
  77. package/dist/checks/consolidated/import-cycles.d.ts +63 -0
  78. package/dist/checks/consolidated/import-cycles.d.ts.map +1 -0
  79. package/dist/checks/consolidated/import-cycles.js +291 -0
  80. package/dist/checks/consolidated/import-cycles.js.map +1 -0
  81. package/dist/checks/consolidated/imports.d.ts +112 -0
  82. package/dist/checks/consolidated/imports.d.ts.map +1 -0
  83. package/dist/checks/consolidated/imports.js +977 -0
  84. package/dist/checks/consolidated/imports.js.map +1 -0
  85. package/dist/checks/consolidated/inline-style-conflicts.d.ts +21 -0
  86. package/dist/checks/consolidated/inline-style-conflicts.d.ts.map +1 -0
  87. package/dist/checks/consolidated/inline-style-conflicts.js +300 -0
  88. package/dist/checks/consolidated/inline-style-conflicts.js.map +1 -0
  89. package/dist/checks/consolidated/lib-organization.d.ts +12 -0
  90. package/dist/checks/consolidated/lib-organization.d.ts.map +1 -0
  91. package/dist/checks/consolidated/lib-organization.js +419 -0
  92. package/dist/checks/consolidated/lib-organization.js.map +1 -0
  93. package/dist/checks/consolidated/n-plus-one.d.ts +63 -0
  94. package/dist/checks/consolidated/n-plus-one.d.ts.map +1 -0
  95. package/dist/checks/consolidated/n-plus-one.js +331 -0
  96. package/dist/checks/consolidated/n-plus-one.js.map +1 -0
  97. package/dist/checks/consolidated/nextjs.d.ts +51 -0
  98. package/dist/checks/consolidated/nextjs.d.ts.map +1 -0
  99. package/dist/checks/consolidated/nextjs.js +205 -0
  100. package/dist/checks/consolidated/nextjs.js.map +1 -0
  101. package/dist/checks/consolidated/organization.d.ts +54 -0
  102. package/dist/checks/consolidated/organization.d.ts.map +1 -0
  103. package/dist/checks/consolidated/organization.js +158 -0
  104. package/dist/checks/consolidated/organization.js.map +1 -0
  105. package/dist/checks/consolidated/pageload.d.ts +12 -0
  106. package/dist/checks/consolidated/pageload.d.ts.map +1 -0
  107. package/dist/checks/consolidated/pageload.js +138 -0
  108. package/dist/checks/consolidated/pageload.js.map +1 -0
  109. package/dist/checks/consolidated/performance.d.ts +112 -0
  110. package/dist/checks/consolidated/performance.d.ts.map +1 -0
  111. package/dist/checks/consolidated/performance.js +1546 -0
  112. package/dist/checks/consolidated/performance.js.map +1 -0
  113. package/dist/checks/consolidated/quality.d.ts +52 -0
  114. package/dist/checks/consolidated/quality.d.ts.map +1 -0
  115. package/dist/checks/consolidated/quality.js +253 -0
  116. package/dist/checks/consolidated/quality.js.map +1 -0
  117. package/dist/checks/consolidated/react.d.ts +48 -0
  118. package/dist/checks/consolidated/react.d.ts.map +1 -0
  119. package/dist/checks/consolidated/react.js +203 -0
  120. package/dist/checks/consolidated/react.js.map +1 -0
  121. package/dist/checks/consolidated/regression-hygiene.d.ts +17 -0
  122. package/dist/checks/consolidated/regression-hygiene.d.ts.map +1 -0
  123. package/dist/checks/consolidated/regression-hygiene.js +242 -0
  124. package/dist/checks/consolidated/regression-hygiene.js.map +1 -0
  125. package/dist/checks/consolidated/regression.d.ts +20 -0
  126. package/dist/checks/consolidated/regression.d.ts.map +1 -0
  127. package/dist/checks/consolidated/regression.js +121 -0
  128. package/dist/checks/consolidated/regression.js.map +1 -0
  129. package/dist/checks/consolidated/runtime.d.ts +53 -0
  130. package/dist/checks/consolidated/runtime.d.ts.map +1 -0
  131. package/dist/checks/consolidated/runtime.js +160 -0
  132. package/dist/checks/consolidated/runtime.js.map +1 -0
  133. package/dist/checks/consolidated/script-performance.d.ts +17 -0
  134. package/dist/checks/consolidated/script-performance.d.ts.map +1 -0
  135. package/dist/checks/consolidated/script-performance.js +137 -0
  136. package/dist/checks/consolidated/script-performance.js.map +1 -0
  137. package/dist/checks/consolidated/security.d.ts +78 -0
  138. package/dist/checks/consolidated/security.d.ts.map +1 -0
  139. package/dist/checks/consolidated/security.js +404 -0
  140. package/dist/checks/consolidated/security.js.map +1 -0
  141. package/dist/checks/consolidated/seo.d.ts +31 -0
  142. package/dist/checks/consolidated/seo.d.ts.map +1 -0
  143. package/dist/checks/consolidated/seo.js +1438 -0
  144. package/dist/checks/consolidated/seo.js.map +1 -0
  145. package/dist/checks/consolidated/sx-prop-deprecation.d.ts +22 -0
  146. package/dist/checks/consolidated/sx-prop-deprecation.d.ts.map +1 -0
  147. package/dist/checks/consolidated/sx-prop-deprecation.js +280 -0
  148. package/dist/checks/consolidated/sx-prop-deprecation.js.map +1 -0
  149. package/dist/checks/consolidated/tailwind-class-validation.d.ts +25 -0
  150. package/dist/checks/consolidated/tailwind-class-validation.d.ts.map +1 -0
  151. package/dist/checks/consolidated/tailwind-class-validation.js +533 -0
  152. package/dist/checks/consolidated/tailwind-class-validation.js.map +1 -0
  153. package/dist/checks/consolidated/testing.d.ts +54 -0
  154. package/dist/checks/consolidated/testing.d.ts.map +1 -0
  155. package/dist/checks/consolidated/testing.js +163 -0
  156. package/dist/checks/consolidated/testing.js.map +1 -0
  157. package/dist/checks/consolidated/typescript.d.ts +3 -0
  158. package/dist/checks/consolidated/typescript.d.ts.map +1 -0
  159. package/dist/checks/consolidated/typescript.js +31 -0
  160. package/dist/checks/consolidated/typescript.js.map +1 -0
  161. package/dist/checks/consolidated/ui-accessibility-advanced.d.ts +104 -0
  162. package/dist/checks/consolidated/ui-accessibility-advanced.d.ts.map +1 -0
  163. package/dist/checks/consolidated/ui-accessibility-advanced.js +689 -0
  164. package/dist/checks/consolidated/ui-accessibility-advanced.js.map +1 -0
  165. package/dist/checks/consolidated/ui-accessibility.d.ts +121 -0
  166. package/dist/checks/consolidated/ui-accessibility.d.ts.map +1 -0
  167. package/dist/checks/consolidated/ui-accessibility.js +776 -0
  168. package/dist/checks/consolidated/ui-accessibility.js.map +1 -0
  169. package/dist/checks/consolidated/ui-advanced-spacing.d.ts +142 -0
  170. package/dist/checks/consolidated/ui-advanced-spacing.d.ts.map +1 -0
  171. package/dist/checks/consolidated/ui-advanced-spacing.js +1220 -0
  172. package/dist/checks/consolidated/ui-advanced-spacing.js.map +1 -0
  173. package/dist/checks/consolidated/ui-animation-duration.d.ts +108 -0
  174. package/dist/checks/consolidated/ui-animation-duration.d.ts.map +1 -0
  175. package/dist/checks/consolidated/ui-animation-duration.js +531 -0
  176. package/dist/checks/consolidated/ui-animation-duration.js.map +1 -0
  177. package/dist/checks/consolidated/ui-border-radius.d.ts +90 -0
  178. package/dist/checks/consolidated/ui-border-radius.d.ts.map +1 -0
  179. package/dist/checks/consolidated/ui-border-radius.js +519 -0
  180. package/dist/checks/consolidated/ui-border-radius.js.map +1 -0
  181. package/dist/checks/consolidated/ui-buttons.d.ts +32 -0
  182. package/dist/checks/consolidated/ui-buttons.d.ts.map +1 -0
  183. package/dist/checks/consolidated/ui-buttons.js +481 -0
  184. package/dist/checks/consolidated/ui-buttons.js.map +1 -0
  185. package/dist/checks/consolidated/ui-cards.d.ts +29 -0
  186. package/dist/checks/consolidated/ui-cards.d.ts.map +1 -0
  187. package/dist/checks/consolidated/ui-cards.js +504 -0
  188. package/dist/checks/consolidated/ui-cards.js.map +1 -0
  189. package/dist/checks/consolidated/ui-checks.d.ts +48 -0
  190. package/dist/checks/consolidated/ui-checks.d.ts.map +1 -0
  191. package/dist/checks/consolidated/ui-checks.js +264 -0
  192. package/dist/checks/consolidated/ui-checks.js.map +1 -0
  193. package/dist/checks/consolidated/ui-cleanup.d.ts +81 -0
  194. package/dist/checks/consolidated/ui-cleanup.d.ts.map +1 -0
  195. package/dist/checks/consolidated/ui-cleanup.js +650 -0
  196. package/dist/checks/consolidated/ui-cleanup.js.map +1 -0
  197. package/dist/checks/consolidated/ui-components.d.ts +255 -0
  198. package/dist/checks/consolidated/ui-components.d.ts.map +1 -0
  199. package/dist/checks/consolidated/ui-components.js +2008 -0
  200. package/dist/checks/consolidated/ui-components.js.map +1 -0
  201. package/dist/checks/consolidated/ui-consistency-advanced.d.ts +130 -0
  202. package/dist/checks/consolidated/ui-consistency-advanced.d.ts.map +1 -0
  203. package/dist/checks/consolidated/ui-consistency-advanced.js +982 -0
  204. package/dist/checks/consolidated/ui-consistency-advanced.js.map +1 -0
  205. package/dist/checks/consolidated/ui-consistency-comprehensive.d.ts +30 -0
  206. package/dist/checks/consolidated/ui-consistency-comprehensive.d.ts.map +1 -0
  207. package/dist/checks/consolidated/ui-consistency-comprehensive.js +1018 -0
  208. package/dist/checks/consolidated/ui-consistency-comprehensive.js.map +1 -0
  209. package/dist/checks/consolidated/ui-consistency-extended.d.ts +26 -0
  210. package/dist/checks/consolidated/ui-consistency-extended.d.ts.map +1 -0
  211. package/dist/checks/consolidated/ui-consistency-extended.js +606 -0
  212. package/dist/checks/consolidated/ui-consistency-extended.js.map +1 -0
  213. package/dist/checks/consolidated/ui-data-display.d.ts +103 -0
  214. package/dist/checks/consolidated/ui-data-display.d.ts.map +1 -0
  215. package/dist/checks/consolidated/ui-data-display.js +740 -0
  216. package/dist/checks/consolidated/ui-data-display.js.map +1 -0
  217. package/dist/checks/consolidated/ui-deprecated.d.ts +22 -0
  218. package/dist/checks/consolidated/ui-deprecated.d.ts.map +1 -0
  219. package/dist/checks/consolidated/ui-deprecated.js +336 -0
  220. package/dist/checks/consolidated/ui-deprecated.js.map +1 -0
  221. package/dist/checks/consolidated/ui-empty-null-states.d.ts +90 -0
  222. package/dist/checks/consolidated/ui-empty-null-states.d.ts.map +1 -0
  223. package/dist/checks/consolidated/ui-empty-null-states.js +511 -0
  224. package/dist/checks/consolidated/ui-empty-null-states.js.map +1 -0
  225. package/dist/checks/consolidated/ui-error-states.d.ts +99 -0
  226. package/dist/checks/consolidated/ui-error-states.d.ts.map +1 -0
  227. package/dist/checks/consolidated/ui-error-states.js +694 -0
  228. package/dist/checks/consolidated/ui-error-states.js.map +1 -0
  229. package/dist/checks/consolidated/ui-feedback-confirmations.d.ts +90 -0
  230. package/dist/checks/consolidated/ui-feedback-confirmations.d.ts.map +1 -0
  231. package/dist/checks/consolidated/ui-feedback-confirmations.js +596 -0
  232. package/dist/checks/consolidated/ui-feedback-confirmations.js.map +1 -0
  233. package/dist/checks/consolidated/ui-forms.d.ts +32 -0
  234. package/dist/checks/consolidated/ui-forms.d.ts.map +1 -0
  235. package/dist/checks/consolidated/ui-forms.js +568 -0
  236. package/dist/checks/consolidated/ui-forms.js.map +1 -0
  237. package/dist/checks/consolidated/ui-gradient-shadow.d.ts +90 -0
  238. package/dist/checks/consolidated/ui-gradient-shadow.d.ts.map +1 -0
  239. package/dist/checks/consolidated/ui-gradient-shadow.js +568 -0
  240. package/dist/checks/consolidated/ui-gradient-shadow.js.map +1 -0
  241. package/dist/checks/consolidated/ui-grid-responsive.d.ts +27 -0
  242. package/dist/checks/consolidated/ui-grid-responsive.d.ts.map +1 -0
  243. package/dist/checks/consolidated/ui-grid-responsive.js +441 -0
  244. package/dist/checks/consolidated/ui-grid-responsive.js.map +1 -0
  245. package/dist/checks/consolidated/ui-icon-size-tokens.d.ts +104 -0
  246. package/dist/checks/consolidated/ui-icon-size-tokens.d.ts.map +1 -0
  247. package/dist/checks/consolidated/ui-icon-size-tokens.js +514 -0
  248. package/dist/checks/consolidated/ui-icon-size-tokens.js.map +1 -0
  249. package/dist/checks/consolidated/ui-iconography.d.ts +90 -0
  250. package/dist/checks/consolidated/ui-iconography.d.ts.map +1 -0
  251. package/dist/checks/consolidated/ui-iconography.js +565 -0
  252. package/dist/checks/consolidated/ui-iconography.js.map +1 -0
  253. package/dist/checks/consolidated/ui-interactive-states.d.ts +240 -0
  254. package/dist/checks/consolidated/ui-interactive-states.d.ts.map +1 -0
  255. package/dist/checks/consolidated/ui-interactive-states.js +2474 -0
  256. package/dist/checks/consolidated/ui-interactive-states.js.map +1 -0
  257. package/dist/checks/consolidated/ui-layout.d.ts +256 -0
  258. package/dist/checks/consolidated/ui-layout.d.ts.map +1 -0
  259. package/dist/checks/consolidated/ui-layout.js +1371 -0
  260. package/dist/checks/consolidated/ui-layout.js.map +1 -0
  261. package/dist/checks/consolidated/ui-loading-skeletons.d.ts +11 -0
  262. package/dist/checks/consolidated/ui-loading-skeletons.d.ts.map +1 -0
  263. package/dist/checks/consolidated/ui-loading-skeletons.js +145 -0
  264. package/dist/checks/consolidated/ui-loading-skeletons.js.map +1 -0
  265. package/dist/checks/consolidated/ui-loading-state-skeletons.d.ts +9 -0
  266. package/dist/checks/consolidated/ui-loading-state-skeletons.d.ts.map +1 -0
  267. package/dist/checks/consolidated/ui-loading-state-skeletons.js +125 -0
  268. package/dist/checks/consolidated/ui-loading-state-skeletons.js.map +1 -0
  269. package/dist/checks/consolidated/ui-media.d.ts +74 -0
  270. package/dist/checks/consolidated/ui-media.d.ts.map +1 -0
  271. package/dist/checks/consolidated/ui-media.js +408 -0
  272. package/dist/checks/consolidated/ui-media.js.map +1 -0
  273. package/dist/checks/consolidated/ui-micro-interactions.d.ts +107 -0
  274. package/dist/checks/consolidated/ui-micro-interactions.d.ts.map +1 -0
  275. package/dist/checks/consolidated/ui-micro-interactions.js +825 -0
  276. package/dist/checks/consolidated/ui-micro-interactions.js.map +1 -0
  277. package/dist/checks/consolidated/ui-microcopy-consistency.d.ts +114 -0
  278. package/dist/checks/consolidated/ui-microcopy-consistency.d.ts.map +1 -0
  279. package/dist/checks/consolidated/ui-microcopy-consistency.js +566 -0
  280. package/dist/checks/consolidated/ui-microcopy-consistency.js.map +1 -0
  281. package/dist/checks/consolidated/ui-mobile-ux.d.ts +251 -0
  282. package/dist/checks/consolidated/ui-mobile-ux.d.ts.map +1 -0
  283. package/dist/checks/consolidated/ui-mobile-ux.js +2212 -0
  284. package/dist/checks/consolidated/ui-mobile-ux.js.map +1 -0
  285. package/dist/checks/consolidated/ui-motion-accessibility.d.ts +93 -0
  286. package/dist/checks/consolidated/ui-motion-accessibility.d.ts.map +1 -0
  287. package/dist/checks/consolidated/ui-motion-accessibility.js +450 -0
  288. package/dist/checks/consolidated/ui-motion-accessibility.js.map +1 -0
  289. package/dist/checks/consolidated/ui-navigation.d.ts +85 -0
  290. package/dist/checks/consolidated/ui-navigation.d.ts.map +1 -0
  291. package/dist/checks/consolidated/ui-navigation.js +673 -0
  292. package/dist/checks/consolidated/ui-navigation.js.map +1 -0
  293. package/dist/checks/consolidated/ui-patterns.d.ts +174 -0
  294. package/dist/checks/consolidated/ui-patterns.d.ts.map +1 -0
  295. package/dist/checks/consolidated/ui-patterns.js +1532 -0
  296. package/dist/checks/consolidated/ui-patterns.js.map +1 -0
  297. package/dist/checks/consolidated/ui-responsive.d.ts +89 -0
  298. package/dist/checks/consolidated/ui-responsive.d.ts.map +1 -0
  299. package/dist/checks/consolidated/ui-responsive.js +588 -0
  300. package/dist/checks/consolidated/ui-responsive.js.map +1 -0
  301. package/dist/checks/consolidated/ui-spacing-standards.d.ts +43 -0
  302. package/dist/checks/consolidated/ui-spacing-standards.d.ts.map +1 -0
  303. package/dist/checks/consolidated/ui-spacing-standards.js +874 -0
  304. package/dist/checks/consolidated/ui-spacing-standards.js.map +1 -0
  305. package/dist/checks/consolidated/ui-spacing.d.ts +751 -0
  306. package/dist/checks/consolidated/ui-spacing.d.ts.map +1 -0
  307. package/dist/checks/consolidated/ui-spacing.js +4996 -0
  308. package/dist/checks/consolidated/ui-spacing.js.map +1 -0
  309. package/dist/checks/consolidated/ui-standards-auto-fixer.d.ts +70 -0
  310. package/dist/checks/consolidated/ui-standards-auto-fixer.d.ts.map +1 -0
  311. package/dist/checks/consolidated/ui-standards-auto-fixer.js +429 -0
  312. package/dist/checks/consolidated/ui-standards-auto-fixer.js.map +1 -0
  313. package/dist/checks/consolidated/ui-standards-enforcement.d.ts +100 -0
  314. package/dist/checks/consolidated/ui-standards-enforcement.d.ts.map +1 -0
  315. package/dist/checks/consolidated/ui-standards-enforcement.js +935 -0
  316. package/dist/checks/consolidated/ui-standards-enforcement.js.map +1 -0
  317. package/dist/checks/consolidated/ui-state-consistency.d.ts +90 -0
  318. package/dist/checks/consolidated/ui-state-consistency.d.ts.map +1 -0
  319. package/dist/checks/consolidated/ui-state-consistency.js +659 -0
  320. package/dist/checks/consolidated/ui-state-consistency.js.map +1 -0
  321. package/dist/checks/consolidated/ui-style-validation.d.ts +74 -0
  322. package/dist/checks/consolidated/ui-style-validation.d.ts.map +1 -0
  323. package/dist/checks/consolidated/ui-style-validation.js +403 -0
  324. package/dist/checks/consolidated/ui-style-validation.js.map +1 -0
  325. package/dist/checks/consolidated/ui-tokens.d.ts +110 -0
  326. package/dist/checks/consolidated/ui-tokens.d.ts.map +1 -0
  327. package/dist/checks/consolidated/ui-tokens.js +990 -0
  328. package/dist/checks/consolidated/ui-tokens.js.map +1 -0
  329. package/dist/checks/consolidated/ui-typography.d.ts +77 -0
  330. package/dist/checks/consolidated/ui-typography.d.ts.map +1 -0
  331. package/dist/checks/consolidated/ui-typography.js +416 -0
  332. package/dist/checks/consolidated/ui-typography.js.map +1 -0
  333. package/dist/checks/consolidated/ui-visual-hierarchy.d.ts +90 -0
  334. package/dist/checks/consolidated/ui-visual-hierarchy.d.ts.map +1 -0
  335. package/dist/checks/consolidated/ui-visual-hierarchy.js +562 -0
  336. package/dist/checks/consolidated/ui-visual-hierarchy.js.map +1 -0
  337. package/dist/checks/consolidated/woocommerce.d.ts +50 -0
  338. package/dist/checks/consolidated/woocommerce.d.ts.map +1 -0
  339. package/dist/checks/consolidated/woocommerce.js +198 -0
  340. package/dist/checks/consolidated/woocommerce.js.map +1 -0
  341. package/dist/checks/core/api-route-protection.d.ts +2 -0
  342. package/dist/checks/core/api-route-protection.d.ts.map +1 -0
  343. package/dist/checks/core/api-route-protection.js +101 -0
  344. package/dist/checks/core/api-route-protection.js.map +1 -0
  345. package/dist/checks/core/critical.d.ts +8 -0
  346. package/dist/checks/core/critical.d.ts.map +1 -0
  347. package/dist/checks/core/critical.js +200 -0
  348. package/dist/checks/core/critical.js.map +1 -0
  349. package/dist/checks/core/database.d.ts +8 -0
  350. package/dist/checks/core/database.d.ts.map +1 -0
  351. package/dist/checks/core/database.js +699 -0
  352. package/dist/checks/core/database.js.map +1 -0
  353. package/dist/checks/core/development.d.ts +8 -0
  354. package/dist/checks/core/development.d.ts.map +1 -0
  355. package/dist/checks/core/development.js +417 -0
  356. package/dist/checks/core/development.js.map +1 -0
  357. package/dist/checks/core/hydration-mismatch-check.d.ts +38 -0
  358. package/dist/checks/core/hydration-mismatch-check.d.ts.map +1 -0
  359. package/dist/checks/core/hydration-mismatch-check.js +411 -0
  360. package/dist/checks/core/hydration-mismatch-check.js.map +1 -0
  361. package/dist/checks/core/performance.d.ts +8 -0
  362. package/dist/checks/core/performance.d.ts.map +1 -0
  363. package/dist/checks/core/performance.js +474 -0
  364. package/dist/checks/core/performance.js.map +1 -0
  365. package/dist/checks/core/security.d.ts +8 -0
  366. package/dist/checks/core/security.d.ts.map +1 -0
  367. package/dist/checks/core/security.js +275 -0
  368. package/dist/checks/core/security.js.map +1 -0
  369. package/dist/checks/core/standardized-error-handling.d.ts +43 -0
  370. package/dist/checks/core/standardized-error-handling.d.ts.map +1 -0
  371. package/dist/checks/core/standardized-error-handling.js +384 -0
  372. package/dist/checks/core/standardized-error-handling.js.map +1 -0
  373. package/dist/checks/core/supercatch.d.ts +8 -0
  374. package/dist/checks/core/supercatch.d.ts.map +1 -0
  375. package/dist/checks/core/supercatch.js +750 -0
  376. package/dist/checks/core/supercatch.js.map +1 -0
  377. package/dist/checks/core/suppression-check.d.ts +2 -0
  378. package/dist/checks/core/suppression-check.d.ts.map +1 -0
  379. package/dist/checks/core/suppression-check.js +129 -0
  380. package/dist/checks/core/suppression-check.js.map +1 -0
  381. package/dist/checks/core/ui-quality.d.ts +8 -0
  382. package/dist/checks/core/ui-quality.d.ts.map +1 -0
  383. package/dist/checks/core/ui-quality.js +1736 -0
  384. package/dist/checks/core/ui-quality.js.map +1 -0
  385. package/dist/checks/core/unused-assets-check.d.ts +2 -0
  386. package/dist/checks/core/unused-assets-check.d.ts.map +1 -0
  387. package/dist/checks/core/unused-assets-check.js +112 -0
  388. package/dist/checks/core/unused-assets-check.js.map +1 -0
  389. package/dist/checks/core/use-status-ssr-safety.d.ts +34 -0
  390. package/dist/checks/core/use-status-ssr-safety.d.ts.map +1 -0
  391. package/dist/checks/core/use-status-ssr-safety.js +283 -0
  392. package/dist/checks/core/use-status-ssr-safety.js.map +1 -0
  393. package/dist/checks/email/email-flow-validation.d.ts +23 -0
  394. package/dist/checks/email/email-flow-validation.d.ts.map +1 -0
  395. package/dist/checks/email/email-flow-validation.js +468 -0
  396. package/dist/checks/email/email-flow-validation.js.map +1 -0
  397. package/dist/checks/email/email-template-db-verification.d.ts +20 -0
  398. package/dist/checks/email/email-template-db-verification.d.ts.map +1 -0
  399. package/dist/checks/email/email-template-db-verification.js +46 -0
  400. package/dist/checks/email/email-template-db-verification.js.map +1 -0
  401. package/dist/checks/email/email-template-validation.d.ts +24 -0
  402. package/dist/checks/email/email-template-validation.d.ts.map +1 -0
  403. package/dist/checks/email/email-template-validation.js +688 -0
  404. package/dist/checks/email/email-template-validation.js.map +1 -0
  405. package/dist/checks/jsx/comment-placement.d.ts +45 -0
  406. package/dist/checks/jsx/comment-placement.d.ts.map +1 -0
  407. package/dist/checks/jsx/comment-placement.js +316 -0
  408. package/dist/checks/jsx/comment-placement.js.map +1 -0
  409. package/dist/checks/specialized/admin-layout-check.d.ts +19 -0
  410. package/dist/checks/specialized/admin-layout-check.d.ts.map +1 -0
  411. package/dist/checks/specialized/admin-layout-check.js +166 -0
  412. package/dist/checks/specialized/admin-layout-check.js.map +1 -0
  413. package/dist/checks/specialized/client-server-separation.d.ts +14 -0
  414. package/dist/checks/specialized/client-server-separation.d.ts.map +1 -0
  415. package/dist/checks/specialized/client-server-separation.js +197 -0
  416. package/dist/checks/specialized/client-server-separation.js.map +1 -0
  417. package/dist/checks/specialized/cost-optimization.d.ts +18 -0
  418. package/dist/checks/specialized/cost-optimization.d.ts.map +1 -0
  419. package/dist/checks/specialized/cost-optimization.js +78 -0
  420. package/dist/checks/specialized/cost-optimization.js.map +1 -0
  421. package/dist/checks/specialized/database-migration-sync.d.ts +21 -0
  422. package/dist/checks/specialized/database-migration-sync.d.ts.map +1 -0
  423. package/dist/checks/specialized/database-migration-sync.js +150 -0
  424. package/dist/checks/specialized/database-migration-sync.js.map +1 -0
  425. package/dist/checks/specialized/database-model-validation.d.ts +15 -0
  426. package/dist/checks/specialized/database-model-validation.d.ts.map +1 -0
  427. package/dist/checks/specialized/database-model-validation.js +35 -0
  428. package/dist/checks/specialized/database-model-validation.js.map +1 -0
  429. package/dist/checks/specialized/database-schema-migrations-diff.d.ts +27 -0
  430. package/dist/checks/specialized/database-schema-migrations-diff.d.ts.map +1 -0
  431. package/dist/checks/specialized/database-schema-migrations-diff.js +177 -0
  432. package/dist/checks/specialized/database-schema-migrations-diff.js.map +1 -0
  433. package/dist/checks/specialized/database-schema-sync.d.ts +23 -0
  434. package/dist/checks/specialized/database-schema-sync.d.ts.map +1 -0
  435. package/dist/checks/specialized/database-schema-sync.js +77 -0
  436. package/dist/checks/specialized/database-schema-sync.js.map +1 -0
  437. package/dist/checks/specialized/decimal-serialization.d.ts +24 -0
  438. package/dist/checks/specialized/decimal-serialization.d.ts.map +1 -0
  439. package/dist/checks/specialized/decimal-serialization.js +400 -0
  440. package/dist/checks/specialized/decimal-serialization.js.map +1 -0
  441. package/dist/checks/specialized/detect-router-issues.d.ts +14 -0
  442. package/dist/checks/specialized/detect-router-issues.d.ts.map +1 -0
  443. package/dist/checks/specialized/detect-router-issues.js +96 -0
  444. package/dist/checks/specialized/detect-router-issues.js.map +1 -0
  445. package/dist/checks/specialized/enum-validation.d.ts +15 -0
  446. package/dist/checks/specialized/enum-validation.d.ts.map +1 -0
  447. package/dist/checks/specialized/enum-validation.js +35 -0
  448. package/dist/checks/specialized/enum-validation.js.map +1 -0
  449. package/dist/checks/specialized/hash-collision.d.ts +18 -0
  450. package/dist/checks/specialized/hash-collision.d.ts.map +1 -0
  451. package/dist/checks/specialized/hash-collision.js +78 -0
  452. package/dist/checks/specialized/hash-collision.js.map +1 -0
  453. package/dist/checks/specialized/id-generation-enforcement.d.ts +16 -0
  454. package/dist/checks/specialized/id-generation-enforcement.d.ts.map +1 -0
  455. package/dist/checks/specialized/id-generation-enforcement.js +307 -0
  456. package/dist/checks/specialized/id-generation-enforcement.js.map +1 -0
  457. package/dist/checks/specialized/image-data-integrity.d.ts +15 -0
  458. package/dist/checks/specialized/image-data-integrity.d.ts.map +1 -0
  459. package/dist/checks/specialized/image-data-integrity.js +79 -0
  460. package/dist/checks/specialized/image-data-integrity.js.map +1 -0
  461. package/dist/checks/specialized/image-health.d.ts +14 -0
  462. package/dist/checks/specialized/image-health.d.ts.map +1 -0
  463. package/dist/checks/specialized/image-health.js +122 -0
  464. package/dist/checks/specialized/image-health.js.map +1 -0
  465. package/dist/checks/specialized/image-metadata-validation.d.ts +14 -0
  466. package/dist/checks/specialized/image-metadata-validation.d.ts.map +1 -0
  467. package/dist/checks/specialized/image-metadata-validation.js +95 -0
  468. package/dist/checks/specialized/image-metadata-validation.js.map +1 -0
  469. package/dist/checks/specialized/image-optimization.d.ts +16 -0
  470. package/dist/checks/specialized/image-optimization.d.ts.map +1 -0
  471. package/dist/checks/specialized/image-optimization.js +86 -0
  472. package/dist/checks/specialized/image-optimization.js.map +1 -0
  473. package/dist/checks/specialized/invalid-module-imports.d.ts +24 -0
  474. package/dist/checks/specialized/invalid-module-imports.d.ts.map +1 -0
  475. package/dist/checks/specialized/invalid-module-imports.js +209 -0
  476. package/dist/checks/specialized/invalid-module-imports.js.map +1 -0
  477. package/dist/checks/specialized/lint-validation.d.ts +26 -0
  478. package/dist/checks/specialized/lint-validation.d.ts.map +1 -0
  479. package/dist/checks/specialized/lint-validation.js +193 -0
  480. package/dist/checks/specialized/lint-validation.js.map +1 -0
  481. package/dist/checks/specialized/listing-workflow.d.ts +19 -0
  482. package/dist/checks/specialized/listing-workflow.d.ts.map +1 -0
  483. package/dist/checks/specialized/listing-workflow.js +89 -0
  484. package/dist/checks/specialized/listing-workflow.js.map +1 -0
  485. package/dist/checks/specialized/mui-imports-validation.d.ts +18 -0
  486. package/dist/checks/specialized/mui-imports-validation.d.ts.map +1 -0
  487. package/dist/checks/specialized/mui-imports-validation.js +134 -0
  488. package/dist/checks/specialized/mui-imports-validation.js.map +1 -0
  489. package/dist/checks/specialized/nextauth-v5-compliance.d.ts +16 -0
  490. package/dist/checks/specialized/nextauth-v5-compliance.d.ts.map +1 -0
  491. package/dist/checks/specialized/nextauth-v5-compliance.js +164 -0
  492. package/dist/checks/specialized/nextauth-v5-compliance.js.map +1 -0
  493. package/dist/checks/specialized/nextjs-params-check.d.ts +14 -0
  494. package/dist/checks/specialized/nextjs-params-check.d.ts.map +1 -0
  495. package/dist/checks/specialized/nextjs-params-check.js +140 -0
  496. package/dist/checks/specialized/nextjs-params-check.js.map +1 -0
  497. package/dist/checks/specialized/no-legacy-catalog-aliases-validation.d.ts +16 -0
  498. package/dist/checks/specialized/no-legacy-catalog-aliases-validation.d.ts.map +1 -0
  499. package/dist/checks/specialized/no-legacy-catalog-aliases-validation.js +36 -0
  500. package/dist/checks/specialized/no-legacy-catalog-aliases-validation.js.map +1 -0
  501. package/dist/checks/specialized/no-wata-cardgraded-validation.d.ts +22 -0
  502. package/dist/checks/specialized/no-wata-cardgraded-validation.d.ts.map +1 -0
  503. package/dist/checks/specialized/no-wata-cardgraded-validation.js +97 -0
  504. package/dist/checks/specialized/no-wata-cardgraded-validation.js.map +1 -0
  505. package/dist/checks/specialized/parameter-consistency-check.d.ts +20 -0
  506. package/dist/checks/specialized/parameter-consistency-check.d.ts.map +1 -0
  507. package/dist/checks/specialized/parameter-consistency-check.js +115 -0
  508. package/dist/checks/specialized/parameter-consistency-check.js.map +1 -0
  509. package/dist/checks/specialized/prisma-field-names-validation.d.ts +15 -0
  510. package/dist/checks/specialized/prisma-field-names-validation.d.ts.map +1 -0
  511. package/dist/checks/specialized/prisma-field-names-validation.js +35 -0
  512. package/dist/checks/specialized/prisma-field-names-validation.js.map +1 -0
  513. package/dist/checks/specialized/prisma-null-syntax.d.ts +34 -0
  514. package/dist/checks/specialized/prisma-null-syntax.d.ts.map +1 -0
  515. package/dist/checks/specialized/prisma-null-syntax.js +330 -0
  516. package/dist/checks/specialized/prisma-null-syntax.js.map +1 -0
  517. package/dist/checks/specialized/prisma-query-validation.d.ts +15 -0
  518. package/dist/checks/specialized/prisma-query-validation.d.ts.map +1 -0
  519. package/dist/checks/specialized/prisma-query-validation.js +35 -0
  520. package/dist/checks/specialized/prisma-query-validation.js.map +1 -0
  521. package/dist/checks/specialized/product-type-validation.d.ts +17 -0
  522. package/dist/checks/specialized/product-type-validation.d.ts.map +1 -0
  523. package/dist/checks/specialized/product-type-validation.js +129 -0
  524. package/dist/checks/specialized/product-type-validation.js.map +1 -0
  525. package/dist/checks/specialized/responsive-image-validation.d.ts +14 -0
  526. package/dist/checks/specialized/responsive-image-validation.d.ts.map +1 -0
  527. package/dist/checks/specialized/responsive-image-validation.js +101 -0
  528. package/dist/checks/specialized/responsive-image-validation.js.map +1 -0
  529. package/dist/checks/specialized/root-cleanliness.d.ts +21 -0
  530. package/dist/checks/specialized/root-cleanliness.d.ts.map +1 -0
  531. package/dist/checks/specialized/root-cleanliness.js +251 -0
  532. package/dist/checks/specialized/root-cleanliness.js.map +1 -0
  533. package/dist/checks/specialized/rotation-detection-validation.d.ts +16 -0
  534. package/dist/checks/specialized/rotation-detection-validation.d.ts.map +1 -0
  535. package/dist/checks/specialized/rotation-detection-validation.js +113 -0
  536. package/dist/checks/specialized/rotation-detection-validation.js.map +1 -0
  537. package/dist/checks/specialized/script-organization.d.ts +17 -0
  538. package/dist/checks/specialized/script-organization.d.ts.map +1 -0
  539. package/dist/checks/specialized/script-organization.js +487 -0
  540. package/dist/checks/specialized/script-organization.js.map +1 -0
  541. package/dist/checks/specialized/shared-components-migration.d.ts +137 -0
  542. package/dist/checks/specialized/shared-components-migration.d.ts.map +1 -0
  543. package/dist/checks/specialized/shared-components-migration.js +1288 -0
  544. package/dist/checks/specialized/shared-components-migration.js.map +1 -0
  545. package/dist/checks/specialized/store-specialties-normalization.d.ts +10 -0
  546. package/dist/checks/specialized/store-specialties-normalization.d.ts.map +1 -0
  547. package/dist/checks/specialized/store-specialties-normalization.js +126 -0
  548. package/dist/checks/specialized/store-specialties-normalization.js.map +1 -0
  549. package/dist/checks/specialized/two-stage-trim-validation.d.ts +16 -0
  550. package/dist/checks/specialized/two-stage-trim-validation.d.ts.map +1 -0
  551. package/dist/checks/specialized/two-stage-trim-validation.js +115 -0
  552. package/dist/checks/specialized/two-stage-trim-validation.js.map +1 -0
  553. package/dist/checks/specialized/underscore-variable-audit.d.ts +26 -0
  554. package/dist/checks/specialized/underscore-variable-audit.d.ts.map +1 -0
  555. package/dist/checks/specialized/underscore-variable-audit.js +219 -0
  556. package/dist/checks/specialized/underscore-variable-audit.js.map +1 -0
  557. package/dist/checks/specialized/unified-badge-consistency.d.ts +16 -0
  558. package/dist/checks/specialized/unified-badge-consistency.d.ts.map +1 -0
  559. package/dist/checks/specialized/unified-badge-consistency.js +284 -0
  560. package/dist/checks/specialized/unified-badge-consistency.js.map +1 -0
  561. package/dist/checks/specialized/validate-integration-enums.d.ts +15 -0
  562. package/dist/checks/specialized/validate-integration-enums.d.ts.map +1 -0
  563. package/dist/checks/specialized/validate-integration-enums.js +131 -0
  564. package/dist/checks/specialized/validate-integration-enums.js.map +1 -0
  565. package/dist/checks/testing/action-regression.d.ts +23 -0
  566. package/dist/checks/testing/action-regression.d.ts.map +1 -0
  567. package/dist/checks/testing/action-regression.js +192 -0
  568. package/dist/checks/testing/action-regression.js.map +1 -0
  569. package/dist/checks/testing/critical-api-coverage.d.ts +21 -0
  570. package/dist/checks/testing/critical-api-coverage.d.ts.map +1 -0
  571. package/dist/checks/testing/critical-api-coverage.js +158 -0
  572. package/dist/checks/testing/critical-api-coverage.js.map +1 -0
  573. package/dist/checks/testing/data-entry-regression-required.d.ts +24 -0
  574. package/dist/checks/testing/data-entry-regression-required.d.ts.map +1 -0
  575. package/dist/checks/testing/data-entry-regression-required.js +378 -0
  576. package/dist/checks/testing/data-entry-regression-required.js.map +1 -0
  577. package/dist/checks/testing/e2e-best-practices.d.ts +24 -0
  578. package/dist/checks/testing/e2e-best-practices.d.ts.map +1 -0
  579. package/dist/checks/testing/e2e-best-practices.js +791 -0
  580. package/dist/checks/testing/e2e-best-practices.js.map +1 -0
  581. package/dist/checks/testing/e2e-flake-patterns.d.ts +26 -0
  582. package/dist/checks/testing/e2e-flake-patterns.d.ts.map +1 -0
  583. package/dist/checks/testing/e2e-flake-patterns.js +305 -0
  584. package/dist/checks/testing/e2e-flake-patterns.js.map +1 -0
  585. package/dist/checks/testing/e2e-redundant-visibility-checks.d.ts +25 -0
  586. package/dist/checks/testing/e2e-redundant-visibility-checks.d.ts.map +1 -0
  587. package/dist/checks/testing/e2e-redundant-visibility-checks.js +613 -0
  588. package/dist/checks/testing/e2e-redundant-visibility-checks.js.map +1 -0
  589. package/dist/checks/testing/e2e-slow-tests.d.ts +9 -0
  590. package/dist/checks/testing/e2e-slow-tests.d.ts.map +1 -0
  591. package/dist/checks/testing/e2e-slow-tests.js +142 -0
  592. package/dist/checks/testing/e2e-slow-tests.js.map +1 -0
  593. package/dist/checks/testing/e2e-timeouts.d.ts +9 -0
  594. package/dist/checks/testing/e2e-timeouts.d.ts.map +1 -0
  595. package/dist/checks/testing/e2e-timeouts.js +82 -0
  596. package/dist/checks/testing/e2e-timeouts.js.map +1 -0
  597. package/dist/checks/testing/integration-e2e-depth.d.ts +20 -0
  598. package/dist/checks/testing/integration-e2e-depth.d.ts.map +1 -0
  599. package/dist/checks/testing/integration-e2e-depth.js +575 -0
  600. package/dist/checks/testing/integration-e2e-depth.js.map +1 -0
  601. package/dist/checks/testing/playwright-feature-coverage-gaps.d.ts +31 -0
  602. package/dist/checks/testing/playwright-feature-coverage-gaps.d.ts.map +1 -0
  603. package/dist/checks/testing/playwright-feature-coverage-gaps.js +1582 -0
  604. package/dist/checks/testing/playwright-feature-coverage-gaps.js.map +1 -0
  605. package/dist/checks/testing/playwright-mock-inventory.d.ts +24 -0
  606. package/dist/checks/testing/playwright-mock-inventory.d.ts.map +1 -0
  607. package/dist/checks/testing/playwright-mock-inventory.js +380 -0
  608. package/dist/checks/testing/playwright-mock-inventory.js.map +1 -0
  609. package/dist/checks/testing/test-coverage-threshold.d.ts +25 -0
  610. package/dist/checks/testing/test-coverage-threshold.d.ts.map +1 -0
  611. package/dist/checks/testing/test-coverage-threshold.js +166 -0
  612. package/dist/checks/testing/test-coverage-threshold.js.map +1 -0
  613. package/dist/checks/testing/test-flakiness-score.d.ts +27 -0
  614. package/dist/checks/testing/test-flakiness-score.d.ts.map +1 -0
  615. package/dist/checks/testing/test-flakiness-score.js +358 -0
  616. package/dist/checks/testing/test-flakiness-score.js.map +1 -0
  617. package/dist/checks/testing/test-patterns.d.ts +16 -0
  618. package/dist/checks/testing/test-patterns.d.ts.map +1 -0
  619. package/dist/checks/testing/test-patterns.js +156 -0
  620. package/dist/checks/testing/test-patterns.js.map +1 -0
  621. package/dist/checks/workflows/a-plus-rating-validation.d.ts +42 -0
  622. package/dist/checks/workflows/a-plus-rating-validation.d.ts.map +1 -0
  623. package/dist/checks/workflows/a-plus-rating-validation.js +527 -0
  624. package/dist/checks/workflows/a-plus-rating-validation.js.map +1 -0
  625. package/dist/checks/workflows/affected.d.ts +14 -0
  626. package/dist/checks/workflows/affected.d.ts.map +1 -0
  627. package/dist/checks/workflows/affected.js +126 -0
  628. package/dist/checks/workflows/affected.js.map +1 -0
  629. package/dist/checks/workflows/ai.d.ts +6 -0
  630. package/dist/checks/workflows/ai.d.ts.map +1 -0
  631. package/dist/checks/workflows/ai.js +42 -0
  632. package/dist/checks/workflows/ai.js.map +1 -0
  633. package/dist/checks/workflows/all.d.ts +31 -0
  634. package/dist/checks/workflows/all.d.ts.map +1 -0
  635. package/dist/checks/workflows/all.js +2688 -0
  636. package/dist/checks/workflows/all.js.map +1 -0
  637. package/dist/checks/workflows/commit.d.ts +19 -0
  638. package/dist/checks/workflows/commit.d.ts.map +1 -0
  639. package/dist/checks/workflows/commit.js +207 -0
  640. package/dist/checks/workflows/commit.js.map +1 -0
  641. package/dist/checks/workflows/critical.d.ts +9 -0
  642. package/dist/checks/workflows/critical.d.ts.map +1 -0
  643. package/dist/checks/workflows/critical.js +213 -0
  644. package/dist/checks/workflows/critical.js.map +1 -0
  645. package/dist/checks/workflows/database-id-validation.d.ts +9 -0
  646. package/dist/checks/workflows/database-id-validation.d.ts.map +1 -0
  647. package/dist/checks/workflows/database-id-validation.js +13 -0
  648. package/dist/checks/workflows/database-id-validation.js.map +1 -0
  649. package/dist/checks/workflows/deploy.d.ts +20 -0
  650. package/dist/checks/workflows/deploy.d.ts.map +1 -0
  651. package/dist/checks/workflows/deploy.js +107 -0
  652. package/dist/checks/workflows/deploy.js.map +1 -0
  653. package/dist/checks/workflows/deployment-readiness.d.ts +12 -0
  654. package/dist/checks/workflows/deployment-readiness.d.ts.map +1 -0
  655. package/dist/checks/workflows/deployment-readiness.js +403 -0
  656. package/dist/checks/workflows/deployment-readiness.js.map +1 -0
  657. package/dist/checks/workflows/dev.d.ts +19 -0
  658. package/dist/checks/workflows/dev.d.ts.map +1 -0
  659. package/dist/checks/workflows/dev.js +88 -0
  660. package/dist/checks/workflows/dev.js.map +1 -0
  661. package/dist/checks/workflows/development.d.ts +9 -0
  662. package/dist/checks/workflows/development.d.ts.map +1 -0
  663. package/dist/checks/workflows/development.js +65 -0
  664. package/dist/checks/workflows/development.js.map +1 -0
  665. package/dist/checks/workflows/enterprise.d.ts +10 -0
  666. package/dist/checks/workflows/enterprise.d.ts.map +1 -0
  667. package/dist/checks/workflows/enterprise.js +359 -0
  668. package/dist/checks/workflows/enterprise.js.map +1 -0
  669. package/dist/checks/workflows/images.d.ts +6 -0
  670. package/dist/checks/workflows/images.d.ts.map +1 -0
  671. package/dist/checks/workflows/images.js +58 -0
  672. package/dist/checks/workflows/images.js.map +1 -0
  673. package/dist/checks/workflows/naming.d.ts +19 -0
  674. package/dist/checks/workflows/naming.d.ts.map +1 -0
  675. package/dist/checks/workflows/naming.js +42 -0
  676. package/dist/checks/workflows/naming.js.map +1 -0
  677. package/dist/checks/workflows/performance.d.ts +8 -0
  678. package/dist/checks/workflows/performance.d.ts.map +1 -0
  679. package/dist/checks/workflows/performance.js +77 -0
  680. package/dist/checks/workflows/performance.js.map +1 -0
  681. package/dist/checks/workflows/pre-deploy.d.ts +6 -0
  682. package/dist/checks/workflows/pre-deploy.d.ts.map +1 -0
  683. package/dist/checks/workflows/pre-deploy.js +41 -0
  684. package/dist/checks/workflows/pre-deploy.js.map +1 -0
  685. package/dist/checks/workflows/security.d.ts +8 -0
  686. package/dist/checks/workflows/security.d.ts.map +1 -0
  687. package/dist/checks/workflows/security.js +71 -0
  688. package/dist/checks/workflows/security.js.map +1 -0
  689. package/dist/checks/workflows/supercatch.d.ts +8 -0
  690. package/dist/checks/workflows/supercatch.d.ts.map +1 -0
  691. package/dist/checks/workflows/supercatch.js +127 -0
  692. package/dist/checks/workflows/supercatch.js.map +1 -0
  693. package/dist/checks/workflows/ui-quality.d.ts +9 -0
  694. package/dist/checks/workflows/ui-quality.d.ts.map +1 -0
  695. package/dist/checks/workflows/ui-quality.js +264 -0
  696. package/dist/checks/workflows/ui-quality.js.map +1 -0
  697. package/dist/checks/workflows/ui-uniformity.d.ts +18 -0
  698. package/dist/checks/workflows/ui-uniformity.d.ts.map +1 -0
  699. package/dist/checks/workflows/ui-uniformity.js +265 -0
  700. package/dist/checks/workflows/ui-uniformity.js.map +1 -0
  701. package/dist/checks/workflows/vercel.d.ts +16 -0
  702. package/dist/checks/workflows/vercel.d.ts.map +1 -0
  703. package/dist/checks/workflows/vercel.js +173 -0
  704. package/dist/checks/workflows/vercel.js.map +1 -0
  705. package/dist/utils/validation-helpers.d.ts +43 -0
  706. package/dist/utils/validation-helpers.d.ts.map +1 -0
  707. package/dist/utils/validation-helpers.js +370 -0
  708. package/dist/utils/validation-helpers.js.map +1 -0
  709. package/package.json +1 -1
@@ -0,0 +1,2212 @@
1
+ #!/usr/bin/env tsx
2
+ "use strict";
3
+ /**
4
+ * UI Mobile UX Preflight (NON-BLOCKING)
5
+ *
6
+ * Comprehensive mobile user experience validation.
7
+ * Ensures the app provides a great experience on mobile devices.
8
+ *
9
+ * Checks:
10
+ * 1. Safe Area Insets - env(safe-area-inset-*) for notched devices
11
+ * 2. Input Types - Proper HTML input types for mobile keyboards
12
+ * 3. Font Size Minimum - 16px minimum to prevent iOS auto-zoom
13
+ * 4. Horizontal Overflow - Elements wider than viewport
14
+ * 5. Viewport Units - 100vh issues, prefer dvh/svh
15
+ * 6. Hover Media Query - @media (hover: hover) for hover-only styles
16
+ * 7. Bottom Navigation - Mobile nav patterns
17
+ * 8. Sticky Position - iOS Safari quirks
18
+ * 9. Orientation Support - Landscape handling
19
+ * 10. Form Labels - Visible labels, not just placeholders
20
+ * 11. Mobile Menu Focus - Focus management in mobile menus
21
+ * 12. Tap Delay - touch-action: manipulation
22
+ *
23
+ * Usage:
24
+ * pnpm preflight:ui-mobile-ux # All checks
25
+ * pnpm preflight:ui-mobile-ux safearea # Safe area only
26
+ * pnpm preflight:ui-mobile-ux inputs # Input types only
27
+ * pnpm preflight:ui-mobile-ux fonts # Font size only
28
+ * pnpm preflight:ui-mobile-ux overflow # Horizontal overflow only
29
+ * pnpm preflight:ui-mobile-ux viewport # Viewport units only
30
+ * pnpm preflight:ui-mobile-ux hover # Hover media query only
31
+ */
32
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ var desc = Object.getOwnPropertyDescriptor(m, k);
35
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
36
+ desc = { enumerable: true, get: function() { return m[k]; } };
37
+ }
38
+ Object.defineProperty(o, k2, desc);
39
+ }) : (function(o, m, k, k2) {
40
+ if (k2 === undefined) k2 = k;
41
+ o[k2] = m[k];
42
+ }));
43
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
44
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
45
+ }) : function(o, v) {
46
+ o["default"] = v;
47
+ });
48
+ var __importStar = (this && this.__importStar) || (function () {
49
+ var ownKeys = function(o) {
50
+ ownKeys = Object.getOwnPropertyNames || function (o) {
51
+ var ar = [];
52
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
53
+ return ar;
54
+ };
55
+ return ownKeys(o);
56
+ };
57
+ return function (mod) {
58
+ if (mod && mod.__esModule) return mod;
59
+ var result = {};
60
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
61
+ __setModuleDefault(result, mod);
62
+ return result;
63
+ };
64
+ })();
65
+ Object.defineProperty(exports, "__esModule", { value: true });
66
+ exports.UIMobileUXModule = void 0;
67
+ const fs = __importStar(require("fs"));
68
+ const path = __importStar(require("path"));
69
+ const console_chars_1 = require("../../utils/console-chars");
70
+ const file_cache_1 = require("../../shared/file-cache");
71
+ const glob_patterns_1 = require("../../shared/glob-patterns");
72
+ const EXCLUDED = [...glob_patterns_1.STANDARD_EXCLUDES, "**/*.test.tsx", "**/*.spec.tsx", "**/*.stories.tsx"];
73
+ // Input type mappings for mobile keyboards
74
+ const INPUT_TYPE_PATTERNS = {
75
+ email: { pattern: /email|e-mail/i, type: "email", keyboard: "email keyboard with @" },
76
+ phone: { pattern: /phone|tel|mobile|cell/i, type: "tel", keyboard: "numeric keypad" },
77
+ url: { pattern: /url|website|link|href/i, type: "url", keyboard: "URL keyboard with .com" },
78
+ number: { pattern: /quantity|amount|count|age|year|zip|postal/i, type: "number", keyboard: "numeric keyboard" },
79
+ search: { pattern: /search|query|find/i, type: "search", keyboard: "search keyboard" },
80
+ };
81
+ class UIMobileUXModule {
82
+ verbose;
83
+ constructor(options = {}) {
84
+ this.verbose = options.verbose || false;
85
+ }
86
+ async getTsxFiles() {
87
+ return file_cache_1.fileCache.getAppAndComponentsTSX();
88
+ }
89
+ async getCssFiles() {
90
+ return file_cache_1.fileCache.getCSSFiles();
91
+ }
92
+ /**
93
+ * Check if line should be skipped due to preflight-ignore comment
94
+ */
95
+ shouldSkipLine(lines, lineIndex) {
96
+ // Check current line and up to 5 previous lines for preflight-ignore
97
+ const startIdx = Math.max(0, lineIndex - 5);
98
+ const contextLines = lines.slice(startIdx, lineIndex + 1).join("\n");
99
+ return contextLines.includes("preflight-ignore");
100
+ }
101
+ /**
102
+ * 1. Safe Area Insets
103
+ * Check for proper safe-area-inset usage for notched devices
104
+ */
105
+ async checkSafeAreaInsets() {
106
+ const startTime = Date.now();
107
+ const issues = [];
108
+ const tsxFiles = await this.getTsxFiles();
109
+ const cssFiles = await this.getCssFiles();
110
+ let hasSafeAreaSupport = false;
111
+ let hasFixedBottomElements = false;
112
+ let hasFixedTopElements = false;
113
+ // Check CSS files for safe-area support
114
+ for (const file of cssFiles) {
115
+ const content = fs.readFileSync(file, "utf8");
116
+ if (/env\(safe-area-inset-/.test(content)) {
117
+ hasSafeAreaSupport = true;
118
+ }
119
+ }
120
+ // Check TSX files
121
+ for (const file of tsxFiles) {
122
+ const content = fs.readFileSync(file, "utf8");
123
+ const lines = content.split("\n");
124
+ if (/env\(safe-area-inset-|safe-area-inset/.test(content)) {
125
+ hasSafeAreaSupport = true;
126
+ }
127
+ for (let i = 0; i < lines.length; i++) {
128
+ const line = lines[i];
129
+ // Check for fixed/sticky bottom elements
130
+ if (/fixed.*bottom-0|sticky.*bottom-0|position:\s*fixed.*bottom:\s*0/.test(line)) {
131
+ hasFixedBottomElements = true;
132
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
133
+ if (!/safe-area|pb-safe|padding-bottom.*env/.test(contextBlock)) {
134
+ issues.push({
135
+ file,
136
+ line: i + 1,
137
+ type: "fixed-bottom-no-safe-area",
138
+ severity: "warning",
139
+ message: "Fixed bottom element without safe area padding",
140
+ suggestion: "Add pb-[env(safe-area-inset-bottom)] for notched devices",
141
+ snippet: line.trim().substring(0, 80),
142
+ });
143
+ }
144
+ }
145
+ // Check for fixed/sticky top elements (status bar area)
146
+ if (/fixed.*top-0|sticky.*top-0|position:\s*fixed.*top:\s*0/.test(line)) {
147
+ hasFixedTopElements = true;
148
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
149
+ if (!/safe-area|pt-safe|padding-top.*env/.test(contextBlock)) {
150
+ issues.push({
151
+ file,
152
+ line: i + 1,
153
+ type: "fixed-top-no-safe-area",
154
+ severity: "info",
155
+ message: "Fixed top element - consider safe area for status bar",
156
+ suggestion: "Add pt-[env(safe-area-inset-top)] if content shouldn't go under status bar",
157
+ snippet: line.trim().substring(0, 80),
158
+ });
159
+ }
160
+ }
161
+ }
162
+ }
163
+ // Global check
164
+ if ((hasFixedBottomElements || hasFixedTopElements) && !hasSafeAreaSupport) {
165
+ issues.push({
166
+ file: "codebase-wide",
167
+ line: 0,
168
+ type: "no-safe-area-support",
169
+ severity: "warning",
170
+ message: "Fixed positioned elements found but no safe-area-inset support detected",
171
+ suggestion: "Add CSS: padding-bottom: env(safe-area-inset-bottom, 0px) for notched devices",
172
+ });
173
+ }
174
+ return {
175
+ name: "Safe Area Insets",
176
+ passed: issues.filter((i) => i.severity === "error").length === 0,
177
+ blocking: false,
178
+ issues,
179
+ duration: Date.now() - startTime,
180
+ };
181
+ }
182
+ /**
183
+ * 2. Input Types for Mobile Keyboards
184
+ * Check that inputs use appropriate types for mobile keyboards
185
+ */
186
+ async checkInputTypes() {
187
+ const startTime = Date.now();
188
+ const issues = [];
189
+ const files = await this.getTsxFiles();
190
+ for (const file of files) {
191
+ const content = fs.readFileSync(file, "utf8");
192
+ const lines = content.split("\n");
193
+ for (let i = 0; i < lines.length; i++) {
194
+ const line = lines[i];
195
+ // Check TextField/Input components
196
+ if (/<(?:TextField|Input|input)[^>]*/.test(line)) {
197
+ const contextBlock = lines.slice(i, Math.min(i + 3, lines.length)).join("\n");
198
+ // Extract label/name/placeholder
199
+ const labelMatch = contextBlock.match(/(?:label|name|placeholder)=["']([^"']+)["']/i);
200
+ if (labelMatch) {
201
+ const label = labelMatch[1].toLowerCase();
202
+ for (const [key, config] of Object.entries(INPUT_TYPE_PATTERNS)) {
203
+ if (config.pattern.test(label)) {
204
+ // Check if correct type is used
205
+ const typeMatch = contextBlock.match(/type=["'](\w+)["']/);
206
+ const currentType = typeMatch ? typeMatch[1] : "text";
207
+ if (currentType === "text" && config.type !== "text") {
208
+ issues.push({
209
+ file,
210
+ line: i + 1,
211
+ type: "input-type-mismatch",
212
+ severity: "info",
213
+ message: `Input "${labelMatch[1]}" uses type="text" but could use type="${config.type}"`,
214
+ suggestion: `Use type="${config.type}" for ${config.keyboard}`,
215
+ snippet: line.trim().substring(0, 80),
216
+ });
217
+ }
218
+ break;
219
+ }
220
+ }
221
+ }
222
+ // Check for inputMode attribute (even better for mobile)
223
+ if (/type=["']number["']/.test(contextBlock) && !/inputMode/.test(contextBlock)) {
224
+ issues.push({
225
+ file,
226
+ line: i + 1,
227
+ type: "number-input-no-inputmode",
228
+ severity: "info",
229
+ message: 'type="number" without inputMode - consider inputMode="numeric" or "decimal"',
230
+ suggestion: 'Add inputMode="numeric" for integer-only or inputMode="decimal" for decimals',
231
+ snippet: line.trim().substring(0, 80),
232
+ });
233
+ }
234
+ }
235
+ }
236
+ }
237
+ return {
238
+ name: "Input Types",
239
+ passed: true, // Info only
240
+ blocking: false,
241
+ issues,
242
+ duration: Date.now() - startTime,
243
+ };
244
+ }
245
+ /**
246
+ * 3. Font Size Minimum (iOS Auto-Zoom Prevention)
247
+ * Check that input font sizes are at least 16px to prevent iOS zoom
248
+ */
249
+ async checkFontSizeMinimum() {
250
+ const startTime = Date.now();
251
+ const issues = [];
252
+ const files = await this.getTsxFiles();
253
+ const cssFiles = await this.getCssFiles();
254
+ // Check CSS for small input font sizes
255
+ for (const file of cssFiles) {
256
+ const content = fs.readFileSync(file, "utf8");
257
+ const lines = content.split("\n");
258
+ for (let i = 0; i < lines.length; i++) {
259
+ const line = lines[i];
260
+ // Check for input/textarea font-size < 16px
261
+ if (/input|textarea|select/i.test(content.substring(Math.max(0, content.indexOf(line) - 100), content.indexOf(line)))) {
262
+ const fontSizeMatch = line.match(/font-size:\s*(\d+(?:\.\d+)?)(px|rem)/);
263
+ if (fontSizeMatch) {
264
+ const size = parseFloat(fontSizeMatch[1]);
265
+ const unit = fontSizeMatch[2];
266
+ const pxSize = unit === "rem" ? size * 16 : size;
267
+ if (pxSize < 16) {
268
+ issues.push({
269
+ file,
270
+ line: i + 1,
271
+ type: "input-font-too-small",
272
+ severity: "warning",
273
+ message: `Input font-size ${fontSizeMatch[1]}${unit} (${pxSize}px) causes iOS auto-zoom`,
274
+ suggestion: "Use at least 16px (1rem) for input font-size to prevent iOS zoom on focus",
275
+ snippet: line.trim().substring(0, 80),
276
+ });
277
+ }
278
+ }
279
+ }
280
+ }
281
+ }
282
+ // Check TSX for small text on inputs
283
+ for (const file of files) {
284
+ const content = fs.readFileSync(file, "utf8");
285
+ const lines = content.split("\n");
286
+ for (let i = 0; i < lines.length; i++) {
287
+ const line = lines[i];
288
+ if (/<(?:TextField|Input|input|textarea|select)/i.test(line)) {
289
+ // Check for text-xs or text-sm classes
290
+ if (/className=["'][^"']*\btext-(?:xs|sm)\b/.test(line)) {
291
+ issues.push({
292
+ file,
293
+ line: i + 1,
294
+ type: "input-text-class-small",
295
+ severity: "warning",
296
+ message: "Input with text-xs/text-sm class may cause iOS auto-zoom",
297
+ suggestion: "Use text-base (16px) or larger for inputs to prevent zoom",
298
+ snippet: line.trim().substring(0, 80),
299
+ });
300
+ }
301
+ }
302
+ }
303
+ }
304
+ return {
305
+ name: "Font Size Minimum",
306
+ passed: issues.filter((i) => i.severity === "error").length === 0,
307
+ blocking: false,
308
+ issues,
309
+ duration: Date.now() - startTime,
310
+ };
311
+ }
312
+ /**
313
+ * 4. Horizontal Overflow Prevention
314
+ * Check for elements that might cause horizontal scroll on mobile
315
+ */
316
+ async checkHorizontalOverflow() {
317
+ const startTime = Date.now();
318
+ const issues = [];
319
+ const files = await this.getTsxFiles();
320
+ for (const file of files) {
321
+ const content = fs.readFileSync(file, "utf8");
322
+ const lines = content.split("\n");
323
+ for (let i = 0; i < lines.length; i++) {
324
+ const line = lines[i];
325
+ // Check for fixed widths larger than typical mobile screens
326
+ const widthMatch = line.match(/(?:width|min-width):\s*["']?(\d+)px/);
327
+ if (widthMatch) {
328
+ const width = parseInt(widthMatch[1]);
329
+ if (width > 500) {
330
+ const contextBlock = lines.slice(Math.max(0, i - 2), Math.min(i + 3, lines.length)).join("\n");
331
+ // Check if it has responsive handling
332
+ if (!/max-width|overflow|sm:|md:|lg:|@media/.test(contextBlock)) {
333
+ issues.push({
334
+ file,
335
+ line: i + 1,
336
+ type: "fixed-width-overflow-risk",
337
+ severity: "info",
338
+ message: `Fixed width ${width}px may cause horizontal scroll on mobile`,
339
+ suggestion: "Add max-width: 100% or use responsive width classes",
340
+ snippet: line.trim().substring(0, 80),
341
+ });
342
+ }
343
+ }
344
+ }
345
+ // Check for w-screen without overflow handling
346
+ if (/\bw-screen\b/.test(line)) {
347
+ const contextBlock = lines.slice(i, Math.min(i + 3, lines.length)).join("\n");
348
+ if (!/overflow-x-hidden|overflow-hidden/.test(contextBlock)) {
349
+ issues.push({
350
+ file,
351
+ line: i + 1,
352
+ type: "w-screen-no-overflow",
353
+ severity: "info",
354
+ message: "w-screen can cause horizontal scroll if content overflows",
355
+ suggestion: "Add overflow-x-hidden to parent or use w-full instead",
356
+ snippet: line.trim().substring(0, 80),
357
+ });
358
+ }
359
+ }
360
+ // Check for negative margins that might cause overflow
361
+ if (/-mx-\d+|-ml-\d+|-mr-\d+/.test(line)) {
362
+ const contextBlock = lines.slice(Math.max(0, i - 3), Math.min(i + 3, lines.length)).join("\n");
363
+ if (!/overflow-hidden|overflow-x-hidden/.test(contextBlock)) {
364
+ issues.push({
365
+ file,
366
+ line: i + 1,
367
+ type: "negative-margin-overflow-risk",
368
+ severity: "info",
369
+ message: "Negative horizontal margin can cause mobile overflow",
370
+ suggestion: "Ensure parent has overflow-hidden or equivalent",
371
+ snippet: line.trim().substring(0, 80),
372
+ });
373
+ }
374
+ }
375
+ }
376
+ }
377
+ return {
378
+ name: "Horizontal Overflow",
379
+ passed: true, // Info only
380
+ blocking: false,
381
+ issues,
382
+ duration: Date.now() - startTime,
383
+ };
384
+ }
385
+ /**
386
+ * 5. Viewport Units (100vh issues)
387
+ * Check for 100vh usage which is problematic on mobile browsers
388
+ */
389
+ async checkViewportUnits() {
390
+ const startTime = Date.now();
391
+ const issues = [];
392
+ const files = await this.getTsxFiles();
393
+ const cssFiles = await this.getCssFiles();
394
+ const allFiles = [...files, ...cssFiles];
395
+ for (const file of allFiles) {
396
+ const content = fs.readFileSync(file, "utf8");
397
+ const lines = content.split("\n");
398
+ for (let i = 0; i < lines.length; i++) {
399
+ const line = lines[i];
400
+ // Check for 100vh usage
401
+ if (/\b100vh\b|h-screen/.test(line)) {
402
+ const contextBlock = lines.slice(Math.max(0, i - 2), Math.min(i + 3, lines.length)).join("\n");
403
+ // Check if modern viewport units are used as fallback
404
+ const hasModernUnits = /\b100dvh\b|\b100svh\b|\b100lvh\b|dvh|svh/.test(contextBlock);
405
+ const hasMinHeight = /min-h-screen|min-height:\s*100vh/.test(contextBlock);
406
+ if (!hasModernUnits) {
407
+ issues.push({
408
+ file,
409
+ line: i + 1,
410
+ type: "100vh-mobile-issue",
411
+ severity: "info",
412
+ message: "100vh/h-screen is unreliable on mobile (address bar changes height)",
413
+ suggestion: "Use min-h-[100dvh] or height: 100dvh for dynamic viewport on mobile",
414
+ snippet: line.trim().substring(0, 80),
415
+ });
416
+ }
417
+ }
418
+ // Check for vh units in animations (can cause reflows on mobile)
419
+ if (/transform.*translateY.*vh|animation.*vh/.test(line)) {
420
+ issues.push({
421
+ file,
422
+ line: i + 1,
423
+ type: "vh-in-animation",
424
+ severity: "info",
425
+ message: "vh units in animations can cause jank on mobile (address bar resize)",
426
+ suggestion: "Use fixed px values or % for smoother animations",
427
+ snippet: line.trim().substring(0, 80),
428
+ });
429
+ }
430
+ }
431
+ }
432
+ return {
433
+ name: "Viewport Units",
434
+ passed: true, // Info only
435
+ blocking: false,
436
+ issues,
437
+ duration: Date.now() - startTime,
438
+ };
439
+ }
440
+ /**
441
+ * 6. Hover Media Query
442
+ * Check that hover styles use @media (hover: hover) for touch devices
443
+ */
444
+ async checkHoverMediaQuery() {
445
+ const startTime = Date.now();
446
+ const issues = [];
447
+ const cssFiles = await this.getCssFiles();
448
+ let hasHoverMediaQuery = false;
449
+ let hoverStyleCount = 0;
450
+ for (const file of cssFiles) {
451
+ const content = fs.readFileSync(file, "utf8");
452
+ if (/@media\s*\(hover:\s*hover\)/.test(content)) {
453
+ hasHoverMediaQuery = true;
454
+ }
455
+ // Count hover styles
456
+ const hoverMatches = content.match(/:hover\s*\{/g);
457
+ if (hoverMatches) {
458
+ hoverStyleCount += hoverMatches.length;
459
+ }
460
+ }
461
+ // Check TSX for complex hover states that might need media query
462
+ const tsxFiles = await this.getTsxFiles();
463
+ for (const file of tsxFiles) {
464
+ const content = fs.readFileSync(file, "utf8");
465
+ const lines = content.split("\n");
466
+ for (let i = 0; i < lines.length; i++) {
467
+ const line = lines[i];
468
+ // Skip lines with preflight-ignore
469
+ if (this.shouldSkipLine(lines, i)) {
470
+ continue;
471
+ }
472
+ // Check for hover state that changes layout/visibility
473
+ if (/hover:(?:block|flex|grid|visible|opacity-100)/.test(line)) {
474
+ const contextBlock = lines.slice(Math.max(0, i - 2), Math.min(i + 3, lines.length)).join("\n");
475
+ // Check if there's a touch/click alternative or mobile-first responsive classes
476
+ if (!/onClick|onTouchStart|group-focus|focus:|md:opacity-0|sm:opacity-0/.test(contextBlock)) {
477
+ issues.push({
478
+ file,
479
+ line: i + 1,
480
+ type: "hover-reveals-content",
481
+ severity: "warning",
482
+ message: "Hover-revealed content is inaccessible on touch devices",
483
+ suggestion: "Add onClick handler or focus state for touch device access",
484
+ snippet: line.trim().substring(0, 80),
485
+ });
486
+ }
487
+ }
488
+ }
489
+ }
490
+ // Global suggestion if many hover styles but no media query
491
+ if (hoverStyleCount > 10 && !hasHoverMediaQuery) {
492
+ issues.push({
493
+ file: "codebase-wide",
494
+ line: 0,
495
+ type: "no-hover-media-query",
496
+ severity: "info",
497
+ message: `${hoverStyleCount} hover styles found - consider @media (hover: hover) for touch devices`,
498
+ suggestion: "Wrap complex hover effects in @media (hover: hover) { } to avoid sticky hover on touch",
499
+ });
500
+ }
501
+ return {
502
+ name: "Hover Media Query",
503
+ passed: issues.filter((i) => i.severity === "error").length === 0,
504
+ blocking: false,
505
+ issues,
506
+ duration: Date.now() - startTime,
507
+ };
508
+ }
509
+ /**
510
+ * 7. Form Labels (Not Just Placeholders)
511
+ * Check that form inputs have visible labels, not just placeholders
512
+ */
513
+ async checkFormLabels() {
514
+ const startTime = Date.now();
515
+ const issues = [];
516
+ const files = await this.getTsxFiles();
517
+ for (const file of files) {
518
+ const content = fs.readFileSync(file, "utf8");
519
+ const lines = content.split("\n");
520
+ for (let i = 0; i < lines.length; i++) {
521
+ const line = lines[i];
522
+ // Check for input with placeholder but no label
523
+ if (/<(?:input|Input|TextField)[^>]*placeholder=/.test(line)) {
524
+ const contextBlock = lines.slice(Math.max(0, i - 5), Math.min(i + 5, lines.length)).join("\n");
525
+ const hasLabel = /label=|<label|<Label|aria-label|aria-labelledby|id=.*for=|htmlFor/.test(contextBlock);
526
+ if (!hasLabel) {
527
+ issues.push({
528
+ file,
529
+ line: i + 1,
530
+ type: "placeholder-only-label",
531
+ severity: "info",
532
+ message: "Input has placeholder but no visible label",
533
+ suggestion: "Add a visible label - placeholders disappear on focus and aren't accessible",
534
+ snippet: line.trim().substring(0, 80),
535
+ });
536
+ }
537
+ }
538
+ }
539
+ }
540
+ return {
541
+ name: "Form Labels",
542
+ passed: true, // Info only
543
+ blocking: false,
544
+ issues,
545
+ duration: Date.now() - startTime,
546
+ };
547
+ }
548
+ /**
549
+ * 8. Sticky Position Mobile Issues
550
+ * Check for sticky positioning that may have iOS Safari issues
551
+ */
552
+ async checkStickyPosition() {
553
+ const startTime = Date.now();
554
+ const issues = [];
555
+ const files = await this.getTsxFiles();
556
+ for (const file of files) {
557
+ const content = fs.readFileSync(file, "utf8");
558
+ const lines = content.split("\n");
559
+ for (let i = 0; i < lines.length; i++) {
560
+ const line = lines[i];
561
+ const trimmedLine = line.trim();
562
+ // Skip comment lines, JSDoc, and type definitions
563
+ if (trimmedLine.startsWith("//") ||
564
+ trimmedLine.startsWith("*") ||
565
+ trimmedLine.startsWith("/**") ||
566
+ trimmedLine.startsWith("/*") ||
567
+ /^\s*\*/.test(line) ||
568
+ /interface\s|type\s|:\s*boolean/.test(line)) {
569
+ continue;
570
+ }
571
+ // Check for sticky in className or style context (not just any word "sticky")
572
+ // Avoid matching "sticky" in variable names like "z-sticky" or "--z-sticky"
573
+ const hasStickyClass = /className=["'][^"']*\bsticky\b(?!-)/.test(line);
574
+ const hasStickyStyle = /position:\s*["']?sticky/.test(line);
575
+ // Skip lines where "sticky" is just in a variable name (e.g., z-[var(--z-sticky)])
576
+ const isStickyInVarName = /var\(--[^)]*sticky|z-sticky/.test(line);
577
+ if ((hasStickyClass || hasStickyStyle) && !isStickyInVarName) {
578
+ // Check 5 lines of context for top/bottom positioning
579
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
580
+ // Sticky can use either top or bottom for positioning
581
+ // Match: top-0, top: 0, top: sticky ? 0, bottom-0, bottom: 0, top: CONSTANT, etc.
582
+ const hasPositioning = /top-\d|top:\s*(?:\w+\s*\?\s*)?[\d\w]|bottom-\d|bottom:\s*(?:\w+\s*\?\s*)?[\d\w]/.test(contextBlock);
583
+ if (!hasPositioning) {
584
+ issues.push({
585
+ file,
586
+ line: i + 1,
587
+ type: "sticky-no-top",
588
+ severity: "warning",
589
+ message: "Sticky element without top/bottom value - won't stick properly",
590
+ suggestion: "Add top-0 (for headers) or bottom-0 (for footers) for sticky positioning",
591
+ snippet: line.trim().substring(0, 80),
592
+ });
593
+ }
594
+ // Check for sticky inside overflow container (common issue)
595
+ // This is harder to detect statically, so just info
596
+ }
597
+ // Check for -webkit-sticky fallback
598
+ if (/position:\s*sticky/.test(line) && !/position:\s*-webkit-sticky/.test(content)) {
599
+ // Modern browsers don't need this anymore, but good to be aware
600
+ }
601
+ }
602
+ }
603
+ return {
604
+ name: "Sticky Position",
605
+ passed: issues.filter((i) => i.severity === "error").length === 0,
606
+ blocking: false,
607
+ issues,
608
+ duration: Date.now() - startTime,
609
+ };
610
+ }
611
+ /**
612
+ * 9. Orientation Support
613
+ * Check for landscape orientation handling
614
+ */
615
+ async checkOrientationSupport() {
616
+ const startTime = Date.now();
617
+ const issues = [];
618
+ const cssFiles = await this.getCssFiles();
619
+ const tsxFiles = await this.getTsxFiles();
620
+ let hasOrientationHandling = false;
621
+ for (const file of cssFiles) {
622
+ const content = fs.readFileSync(file, "utf8");
623
+ if (/orientation:\s*(?:landscape|portrait)/.test(content)) {
624
+ hasOrientationHandling = true;
625
+ }
626
+ }
627
+ // Check for full-screen modals that might need orientation handling
628
+ for (const file of tsxFiles) {
629
+ const content = fs.readFileSync(file, "utf8");
630
+ const lines = content.split("\n");
631
+ for (let i = 0; i < lines.length; i++) {
632
+ const line = lines[i];
633
+ // Full-screen overlays should handle both orientations
634
+ if (/\bh-screen\b.*\bw-screen\b|\bfixed\b.*\binset-0\b/.test(line)) {
635
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
636
+ if (!/overflow|scroll|landscape|orientation/.test(contextBlock)) {
637
+ issues.push({
638
+ file,
639
+ line: i + 1,
640
+ type: "fullscreen-no-scroll",
641
+ severity: "info",
642
+ message: "Full-screen element - ensure content is scrollable in landscape mode",
643
+ suggestion: "Add overflow-y-auto for landscape orientation support",
644
+ snippet: line.trim().substring(0, 80),
645
+ });
646
+ }
647
+ }
648
+ }
649
+ }
650
+ return {
651
+ name: "Orientation Support",
652
+ passed: true, // Info only
653
+ blocking: false,
654
+ issues,
655
+ duration: Date.now() - startTime,
656
+ };
657
+ }
658
+ /**
659
+ * 10. Touch Action Optimization
660
+ * Check for touch-action: manipulation for better touch performance
661
+ */
662
+ async checkTouchAction() {
663
+ const startTime = Date.now();
664
+ const issues = [];
665
+ const cssFiles = await this.getCssFiles();
666
+ const tsxFiles = await this.getTsxFiles();
667
+ let hasTouchActionManipulation = false;
668
+ let hasDoubleClickElements = false;
669
+ // Check global CSS for touch-action
670
+ for (const file of cssFiles) {
671
+ const content = fs.readFileSync(file, "utf8");
672
+ if (/touch-action:\s*manipulation/.test(content)) {
673
+ hasTouchActionManipulation = true;
674
+ }
675
+ }
676
+ // Check TSX files
677
+ for (const file of tsxFiles) {
678
+ const content = fs.readFileSync(file, "utf8");
679
+ if (/touch-manipulation|touch-action.*manipulation/.test(content)) {
680
+ hasTouchActionManipulation = true;
681
+ }
682
+ if (/onDoubleClick|dblclick/.test(content)) {
683
+ hasDoubleClickElements = true;
684
+ }
685
+ }
686
+ // Global suggestion if no touch-action: manipulation
687
+ if (!hasTouchActionManipulation) {
688
+ issues.push({
689
+ file: "codebase-wide",
690
+ line: 0,
691
+ type: "no-touch-action-manipulation",
692
+ severity: "info",
693
+ message: "No global touch-action: manipulation detected",
694
+ suggestion: "Add touch-action: manipulation to <html> or clickable elements to remove 300ms tap delay",
695
+ });
696
+ }
697
+ return {
698
+ name: "Touch Action Optimization",
699
+ passed: true, // Info only
700
+ blocking: false,
701
+ issues,
702
+ duration: Date.now() - startTime,
703
+ };
704
+ }
705
+ /**
706
+ * 11. Mobile Menu Focus Management
707
+ * Check that mobile menus trap focus properly
708
+ */
709
+ async checkMobileMenuFocus() {
710
+ const startTime = Date.now();
711
+ const issues = [];
712
+ const files = await this.getTsxFiles();
713
+ for (const file of files) {
714
+ const content = fs.readFileSync(file, "utf8");
715
+ const lines = content.split("\n");
716
+ for (let i = 0; i < lines.length; i++) {
717
+ const line = lines[i];
718
+ // Check for mobile menu/drawer components
719
+ if (/MobileMenu|MobileNav|Drawer|Sheet|Sidebar.*mobile/i.test(line)) {
720
+ const contextBlock = lines.slice(i, Math.min(i + 30, lines.length)).join("\n");
721
+ // Check for focus trap
722
+ const hasFocusTrap = /FocusTrap|focus-trap|trapFocus|inert|aria-modal/.test(contextBlock);
723
+ if (!hasFocusTrap) {
724
+ issues.push({
725
+ file,
726
+ line: i + 1,
727
+ type: "mobile-menu-no-focus-trap",
728
+ severity: "info",
729
+ message: "Mobile menu/drawer may lack focus trap",
730
+ suggestion: "Add FocusTrap component or aria-modal for accessibility",
731
+ snippet: line.trim().substring(0, 80),
732
+ });
733
+ }
734
+ // Check for close on escape
735
+ const hasEscapeClose = /Escape|onKeyDown|handleKeyDown/.test(contextBlock);
736
+ if (!hasEscapeClose) {
737
+ issues.push({
738
+ file,
739
+ line: i + 1,
740
+ type: "mobile-menu-no-escape",
741
+ severity: "info",
742
+ message: "Mobile menu may not close on Escape key",
743
+ suggestion: "Add keyboard handler to close on Escape press",
744
+ snippet: line.trim().substring(0, 80),
745
+ });
746
+ }
747
+ }
748
+ }
749
+ }
750
+ return {
751
+ name: "Mobile Menu Focus",
752
+ passed: true, // Info only
753
+ blocking: false,
754
+ issues,
755
+ duration: Date.now() - startTime,
756
+ };
757
+ }
758
+ /**
759
+ * 12. Bottom Navigation Pattern
760
+ * Check for proper mobile bottom navigation patterns
761
+ */
762
+ async checkBottomNavigation() {
763
+ const startTime = Date.now();
764
+ const issues = [];
765
+ const files = await this.getTsxFiles();
766
+ let hasBottomNav = false;
767
+ for (const file of files) {
768
+ const content = fs.readFileSync(file, "utf8");
769
+ const lines = content.split("\n");
770
+ for (let i = 0; i < lines.length; i++) {
771
+ const line = lines[i];
772
+ // Skip lines with preflight-ignore or comment lines
773
+ if (this.shouldSkipLine(lines, i)) {
774
+ continue;
775
+ }
776
+ const trimmedLine = line.trim();
777
+ if (trimmedLine.startsWith("//") || trimmedLine.startsWith("*") || trimmedLine.startsWith("{/*")) {
778
+ continue;
779
+ }
780
+ // Check for bottom navigation in actual code (not comments)
781
+ // Only match fixed bottom-positioned elements (viewport level), not absolute (container level)
782
+ const matchesBottomNav = /BottomNav|MobileTabBar/i.test(line) ||
783
+ /fixed\s+bottom-0|bottom-0\s+.*fixed|fixed.*\s+bottom-\d/.test(line);
784
+ if (matchesBottomNav) {
785
+ hasBottomNav = true;
786
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
787
+ // Check for safe area padding
788
+ if (!/safe-area|pb-safe|padding-bottom.*env/.test(contextBlock)) {
789
+ issues.push({
790
+ file,
791
+ line: i + 1,
792
+ type: "bottom-nav-no-safe-area",
793
+ severity: "warning",
794
+ message: "Bottom navigation without safe area padding",
795
+ suggestion: "Add pb-[env(safe-area-inset-bottom)] for notched devices",
796
+ snippet: line.trim().substring(0, 80),
797
+ });
798
+ }
799
+ // Check for adequate touch targets
800
+ if (/h-\[(\d+)px\]|height:\s*(\d+)px/.test(contextBlock)) {
801
+ const heightMatch = contextBlock.match(/h-\[(\d+)px\]|height:\s*(\d+)px/);
802
+ if (heightMatch) {
803
+ const height = parseInt(heightMatch[1] || heightMatch[2]);
804
+ if (height < 48) {
805
+ issues.push({
806
+ file,
807
+ line: i + 1,
808
+ type: "bottom-nav-too-short",
809
+ severity: "warning",
810
+ message: `Bottom nav height ${height}px is below 48px minimum`,
811
+ suggestion: "Increase height to at least 48px for comfortable touch targets",
812
+ snippet: line.trim().substring(0, 80),
813
+ });
814
+ }
815
+ }
816
+ }
817
+ }
818
+ }
819
+ }
820
+ return {
821
+ name: "Bottom Navigation",
822
+ passed: issues.filter((i) => i.severity === "error").length === 0,
823
+ blocking: false,
824
+ issues,
825
+ duration: Date.now() - startTime,
826
+ };
827
+ }
828
+ /**
829
+ * 13. Pull-to-Refresh Detection
830
+ * Check if scrollable lists implement pull-to-refresh patterns
831
+ */
832
+ async checkPullToRefresh() {
833
+ const startTime = Date.now();
834
+ const issues = [];
835
+ const files = await this.getTsxFiles();
836
+ for (const file of files) {
837
+ const content = fs.readFileSync(file, "utf8");
838
+ const lines = content.split("\n");
839
+ for (let i = 0; i < lines.length; i++) {
840
+ const line = lines[i];
841
+ // Check for scrollable list components that might need pull-to-refresh
842
+ if (/useInfiniteQuery|InfiniteScroll|VirtualList|virtualized/i.test(line)) {
843
+ const contextBlock = lines.slice(Math.max(0, i - 10), Math.min(i + 20, lines.length)).join("\n");
844
+ const hasPullToRefresh = /pullToRefresh|onRefresh|RefreshControl|pull.*refresh|usePullToRefresh/i.test(contextBlock);
845
+ const hasRefetchButton = /refetch|refresh.*button|reload/i.test(contextBlock);
846
+ if (!hasPullToRefresh && !hasRefetchButton) {
847
+ issues.push({
848
+ file,
849
+ line: i + 1,
850
+ type: "infinite-list-no-refresh",
851
+ severity: "info",
852
+ message: "Infinite/virtual list without pull-to-refresh or refresh button",
853
+ suggestion: "Add pull-to-refresh for mobile or a visible refresh button",
854
+ snippet: line.trim().substring(0, 80),
855
+ });
856
+ }
857
+ }
858
+ // Check for overscroll-behavior on scrollable containers
859
+ if (/overflow-y-auto|overflow-y-scroll|overflow-auto/.test(line)) {
860
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
861
+ // overscroll-contain prevents pull-to-refresh from triggering page refresh
862
+ if (!/overscroll-contain|overscroll-behavior/.test(contextBlock)) {
863
+ // Only flag if it looks like a main scrollable area
864
+ if (/h-full|h-screen|flex-1/.test(contextBlock)) {
865
+ issues.push({
866
+ file,
867
+ line: i + 1,
868
+ type: "scrollable-no-overscroll",
869
+ severity: "info",
870
+ message: "Main scrollable container without overscroll-behavior",
871
+ suggestion: "Add overscroll-contain to prevent scroll chaining on mobile",
872
+ snippet: line.trim().substring(0, 80),
873
+ });
874
+ }
875
+ }
876
+ }
877
+ }
878
+ }
879
+ return {
880
+ name: "Pull-to-Refresh",
881
+ passed: true, // Info only
882
+ blocking: false,
883
+ issues,
884
+ duration: Date.now() - startTime,
885
+ };
886
+ }
887
+ /**
888
+ * 14. Mobile Table Patterns
889
+ * Check that tables are responsive on mobile (scrollable or card-based)
890
+ */
891
+ async checkMobileTablePatterns() {
892
+ const startTime = Date.now();
893
+ const issues = [];
894
+ const files = await this.getTsxFiles();
895
+ for (const file of files) {
896
+ const content = fs.readFileSync(file, "utf8");
897
+ const lines = content.split("\n");
898
+ for (let i = 0; i < lines.length; i++) {
899
+ const line = lines[i];
900
+ // Skip lines with preflight-ignore
901
+ if (this.shouldSkipLine(lines, i)) {
902
+ continue;
903
+ }
904
+ // Check for table elements
905
+ if (/<(?:Table|table)\b/.test(line)) {
906
+ const contextBlock = lines.slice(Math.max(0, i - 5), Math.min(i + 15, lines.length)).join("\n");
907
+ // Check for responsive handling (Tailwind classes or inline styles)
908
+ const hasOverflowScroll = /overflow-x-auto|overflow-x-scroll|overflow-auto|overflow:\s*["']?auto/.test(contextBlock);
909
+ const hasMobileCard = /md:|lg:|sm:|hidden.*table|table.*hidden|MobileCard|CardView/i.test(contextBlock);
910
+ const hasResponsiveWrapper = /TableContainer|responsive|mobile/i.test(contextBlock);
911
+ if (!hasOverflowScroll && !hasMobileCard && !hasResponsiveWrapper) {
912
+ issues.push({
913
+ file,
914
+ line: i + 1,
915
+ type: "table-not-mobile-responsive",
916
+ severity: "warning",
917
+ message: "Table without mobile-responsive handling",
918
+ suggestion: "Wrap in overflow-x-auto container or use responsive card pattern on mobile",
919
+ snippet: line.trim().substring(0, 80),
920
+ });
921
+ }
922
+ // Check for minimum column widths that might cause issues
923
+ if (/min-w-\[(\d+)px\]/.test(contextBlock)) {
924
+ const minWidthMatch = contextBlock.match(/min-w-\[(\d+)px\]/g);
925
+ if (minWidthMatch && minWidthMatch.length > 3) {
926
+ issues.push({
927
+ file,
928
+ line: i + 1,
929
+ type: "table-many-min-widths",
930
+ severity: "info",
931
+ message: "Table with multiple min-width columns may be wide on mobile",
932
+ suggestion: "Consider hiding less important columns on mobile with hidden md:table-cell",
933
+ snippet: line.trim().substring(0, 80),
934
+ });
935
+ }
936
+ }
937
+ }
938
+ }
939
+ }
940
+ return {
941
+ name: "Mobile Table Patterns",
942
+ passed: issues.filter((i) => i.severity === "error").length === 0,
943
+ blocking: false,
944
+ issues,
945
+ duration: Date.now() - startTime,
946
+ };
947
+ }
948
+ /**
949
+ * 15. Mobile Modal Sizing
950
+ * Check that modals are appropriately sized on mobile
951
+ */
952
+ async checkMobileModalSizing() {
953
+ const startTime = Date.now();
954
+ const issues = [];
955
+ const files = await this.getTsxFiles();
956
+ for (const file of files) {
957
+ const content = fs.readFileSync(file, "utf8");
958
+ const lines = content.split("\n");
959
+ for (let i = 0; i < lines.length; i++) {
960
+ const line = lines[i];
961
+ // Check for modal/dialog components
962
+ if (/<(?:Dialog|Modal|Sheet|Drawer)\b/i.test(line)) {
963
+ const contextBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
964
+ // Check for responsive width
965
+ const hasResponsiveWidth = /w-full|max-w-.*sm:|sm:max-w|md:max-w|inset-x-0/.test(contextBlock);
966
+ const hasFixedSmallWidth = /max-w-(?:xs|sm)\b/.test(contextBlock);
967
+ const isBottomSheet = /Sheet|bottom|slide-up/i.test(line);
968
+ if (!hasResponsiveWidth && !isBottomSheet) {
969
+ // Check for fixed pixel widths
970
+ const fixedWidthMatch = contextBlock.match(/w-\[(\d+)px\]|width:\s*(\d+)px/);
971
+ if (fixedWidthMatch) {
972
+ const width = parseInt(fixedWidthMatch[1] || fixedWidthMatch[2]);
973
+ if (width > 400 && width < 600) {
974
+ issues.push({
975
+ file,
976
+ line: i + 1,
977
+ type: "modal-fixed-width",
978
+ severity: "info",
979
+ message: `Modal with fixed ${width}px width may not fit mobile screens`,
980
+ suggestion: "Use w-full sm:max-w-md for responsive modal sizing",
981
+ snippet: line.trim().substring(0, 80),
982
+ });
983
+ }
984
+ }
985
+ }
986
+ // Check for modal padding on mobile
987
+ if (!/p-4|p-6|px-4|px-6|sm:p-/.test(contextBlock)) {
988
+ issues.push({
989
+ file,
990
+ line: i + 1,
991
+ type: "modal-no-padding",
992
+ severity: "info",
993
+ message: "Modal may lack adequate padding for mobile",
994
+ suggestion: "Add p-4 or p-6 for comfortable touch spacing",
995
+ snippet: line.trim().substring(0, 80),
996
+ });
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ return {
1002
+ name: "Mobile Modal Sizing",
1003
+ passed: true, // Info only
1004
+ blocking: false,
1005
+ issues,
1006
+ duration: Date.now() - startTime,
1007
+ };
1008
+ }
1009
+ /**
1010
+ * 16. iOS Rubber Banding (overscroll-behavior)
1011
+ * Check for proper overscroll handling on iOS
1012
+ */
1013
+ async checkOverscrollBehavior() {
1014
+ const startTime = Date.now();
1015
+ const issues = [];
1016
+ const files = await this.getTsxFiles();
1017
+ const cssFiles = await this.getCssFiles();
1018
+ let hasGlobalOverscroll = false;
1019
+ // Check CSS for global overscroll
1020
+ for (const file of cssFiles) {
1021
+ const content = fs.readFileSync(file, "utf8");
1022
+ if (/overscroll-behavior:\s*(?:none|contain)/.test(content)) {
1023
+ hasGlobalOverscroll = true;
1024
+ }
1025
+ }
1026
+ for (const file of files) {
1027
+ const content = fs.readFileSync(file, "utf8");
1028
+ const lines = content.split("\n");
1029
+ // Check for overscroll classes
1030
+ if (/overscroll-(?:none|contain|auto)/.test(content)) {
1031
+ hasGlobalOverscroll = true;
1032
+ }
1033
+ for (let i = 0; i < lines.length; i++) {
1034
+ const line = lines[i];
1035
+ // Check for nested scrollable containers (can cause scroll chaining)
1036
+ if (/overflow-(?:y-auto|y-scroll|auto)/.test(line)) {
1037
+ const contextBlock = lines.slice(Math.max(0, i - 10), Math.min(i + 10, lines.length)).join("\n");
1038
+ // Check if this is nested inside another scrollable
1039
+ const isNested = (contextBlock.match(/overflow-(?:y-auto|y-scroll|auto)/g) || []).length > 1;
1040
+ if (isNested && !/overscroll-contain/.test(contextBlock)) {
1041
+ issues.push({
1042
+ file,
1043
+ line: i + 1,
1044
+ type: "nested-scroll-no-overscroll",
1045
+ severity: "info",
1046
+ message: "Nested scrollable container without overscroll-contain",
1047
+ suggestion: "Add overscroll-contain to prevent scroll chaining issues",
1048
+ snippet: line.trim().substring(0, 80),
1049
+ });
1050
+ }
1051
+ }
1052
+ // Check for modals/drawers that should trap scroll
1053
+ if (/<(?:Dialog|Modal|Drawer|Sheet)\b/i.test(line)) {
1054
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
1055
+ if (!/overscroll-contain|overflow-hidden/.test(contextBlock)) {
1056
+ issues.push({
1057
+ file,
1058
+ line: i + 1,
1059
+ type: "modal-no-scroll-trap",
1060
+ severity: "info",
1061
+ message: "Modal/drawer may allow background scroll on iOS",
1062
+ suggestion: "Add overscroll-contain or use body scroll lock",
1063
+ snippet: line.trim().substring(0, 80),
1064
+ });
1065
+ }
1066
+ }
1067
+ }
1068
+ }
1069
+ return {
1070
+ name: "Overscroll Behavior",
1071
+ passed: true, // Info only
1072
+ blocking: false,
1073
+ issues,
1074
+ duration: Date.now() - startTime,
1075
+ };
1076
+ }
1077
+ /**
1078
+ * 17. PWA Meta Tags
1079
+ * Check for Progressive Web App meta tag support
1080
+ */
1081
+ async checkPWAMetaTags() {
1082
+ const startTime = Date.now();
1083
+ const issues = [];
1084
+ const files = await this.getTsxFiles();
1085
+ let hasViewportFitCover = false;
1086
+ let hasThemeColor = false;
1087
+ let hasAppleTouchIcon = false;
1088
+ let hasManifest = false;
1089
+ let hasStatusBarStyle = false;
1090
+ // Check layout/root files
1091
+ for (const file of files) {
1092
+ if (!/layout|_app|_document|head/i.test(file))
1093
+ continue;
1094
+ const content = fs.readFileSync(file, "utf8");
1095
+ if (/viewport-fit\s*=\s*cover/.test(content)) {
1096
+ hasViewportFitCover = true;
1097
+ }
1098
+ if (/theme-color/.test(content)) {
1099
+ hasThemeColor = true;
1100
+ }
1101
+ if (/apple-touch-icon/.test(content)) {
1102
+ hasAppleTouchIcon = true;
1103
+ }
1104
+ if (/manifest\.json|manifest\.webmanifest/.test(content)) {
1105
+ hasManifest = true;
1106
+ }
1107
+ if (/apple-mobile-web-app-status-bar-style/.test(content)) {
1108
+ hasStatusBarStyle = true;
1109
+ }
1110
+ }
1111
+ // Report missing PWA features
1112
+ if (!hasViewportFitCover) {
1113
+ issues.push({
1114
+ file: "layout.tsx or _app.tsx",
1115
+ line: 0,
1116
+ type: "missing-viewport-fit",
1117
+ severity: "info",
1118
+ message: "Missing viewport-fit=cover for notched device support",
1119
+ suggestion: "Add <meta name=\"viewport\" content=\"..., viewport-fit=cover\">",
1120
+ });
1121
+ }
1122
+ if (!hasThemeColor) {
1123
+ issues.push({
1124
+ file: "layout.tsx or _app.tsx",
1125
+ line: 0,
1126
+ type: "missing-theme-color",
1127
+ severity: "info",
1128
+ message: "Missing theme-color meta tag for browser chrome styling",
1129
+ suggestion: "Add <meta name=\"theme-color\" content=\"#yourColor\">",
1130
+ });
1131
+ }
1132
+ if (!hasAppleTouchIcon) {
1133
+ issues.push({
1134
+ file: "layout.tsx or _app.tsx",
1135
+ line: 0,
1136
+ type: "missing-apple-touch-icon",
1137
+ severity: "info",
1138
+ message: "Missing apple-touch-icon for iOS home screen",
1139
+ suggestion: "Add <link rel=\"apple-touch-icon\" href=\"/icon-180.png\">",
1140
+ });
1141
+ }
1142
+ if (!hasManifest) {
1143
+ issues.push({
1144
+ file: "layout.tsx or _app.tsx",
1145
+ line: 0,
1146
+ type: "missing-manifest",
1147
+ severity: "info",
1148
+ message: "Missing web app manifest for PWA support",
1149
+ suggestion: "Add <link rel=\"manifest\" href=\"/manifest.json\">",
1150
+ });
1151
+ }
1152
+ return {
1153
+ name: "PWA Meta Tags",
1154
+ passed: true, // Info only
1155
+ blocking: false,
1156
+ issues,
1157
+ duration: Date.now() - startTime,
1158
+ };
1159
+ }
1160
+ /**
1161
+ * 18. Mobile Autofill Styling
1162
+ * Check for handling of browser autofill background colors
1163
+ */
1164
+ async checkMobileAutofillStyling() {
1165
+ const startTime = Date.now();
1166
+ const issues = [];
1167
+ const files = await this.getTsxFiles();
1168
+ const cssFiles = await this.getCssFiles();
1169
+ let hasAutofillStyling = false;
1170
+ // Check CSS for autofill handling
1171
+ for (const file of cssFiles) {
1172
+ const content = fs.readFileSync(file, "utf8");
1173
+ if (/:autofill|:-webkit-autofill/.test(content)) {
1174
+ hasAutofillStyling = true;
1175
+ }
1176
+ }
1177
+ // Check TSX for autofill handling
1178
+ for (const file of files) {
1179
+ const content = fs.readFileSync(file, "utf8");
1180
+ if (/autofill|:-webkit-autofill/.test(content)) {
1181
+ hasAutofillStyling = true;
1182
+ }
1183
+ }
1184
+ // Count input fields
1185
+ let inputCount = 0;
1186
+ for (const file of files) {
1187
+ const content = fs.readFileSync(file, "utf8");
1188
+ const matches = content.match(/<(?:input|Input|TextField)/gi);
1189
+ if (matches)
1190
+ inputCount += matches.length;
1191
+ }
1192
+ if (inputCount > 10 && !hasAutofillStyling) {
1193
+ issues.push({
1194
+ file: "codebase-wide",
1195
+ line: 0,
1196
+ type: "no-autofill-styling",
1197
+ severity: "info",
1198
+ message: `${inputCount} inputs found but no autofill styling detected`,
1199
+ suggestion: "Add CSS: input:-webkit-autofill { -webkit-box-shadow: 0 0 0 30px var(--bg-primary) inset; }",
1200
+ });
1201
+ }
1202
+ return {
1203
+ name: "Mobile Autofill Styling",
1204
+ passed: true, // Info only
1205
+ blocking: false,
1206
+ issues,
1207
+ duration: Date.now() - startTime,
1208
+ };
1209
+ }
1210
+ /**
1211
+ * 19. Thumb Zone Ergonomics
1212
+ * Check that important actions are in thumb-reachable zones
1213
+ */
1214
+ async checkThumbZoneErgonomics() {
1215
+ const startTime = Date.now();
1216
+ const issues = [];
1217
+ const files = await this.getTsxFiles();
1218
+ for (const file of files) {
1219
+ const content = fs.readFileSync(file, "utf8");
1220
+ const lines = content.split("\n");
1221
+ for (let i = 0; i < lines.length; i++) {
1222
+ const line = lines[i];
1223
+ // Check for primary actions that might be at the top
1224
+ if (/(?:primary|submit|save|confirm|add|create)\s*(?:button|action)/i.test(line) ||
1225
+ /<Button[^>]*variant=["'](?:default|primary)["']/.test(line)) {
1226
+ const contextBlock = lines.slice(Math.max(0, i - 10), Math.min(i + 5, lines.length)).join("\n");
1227
+ // Check if it's at the top of the page
1228
+ const isAtTop = /fixed.*top|sticky.*top|header|PageHeader|AppBar/i.test(contextBlock);
1229
+ const hasBottomAlternative = /fixed.*bottom|sticky.*bottom|BottomNav|MobileActions/i.test(content);
1230
+ if (isAtTop && !hasBottomAlternative) {
1231
+ issues.push({
1232
+ file,
1233
+ line: i + 1,
1234
+ type: "primary-action-not-thumb-zone",
1235
+ severity: "info",
1236
+ message: "Primary action button at top - may be hard to reach on mobile",
1237
+ suggestion: "Consider duplicating important actions in bottom bar for mobile thumb zone",
1238
+ snippet: line.trim().substring(0, 80),
1239
+ });
1240
+ }
1241
+ }
1242
+ // Check for FAB (Floating Action Button) positioning
1243
+ if (/FloatingActionButton|fab|FAB/.test(line)) {
1244
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
1245
+ // FAB should be bottom-right for right-handed users
1246
+ const isBottomRight = /bottom.*right|right.*bottom/.test(contextBlock);
1247
+ if (!isBottomRight) {
1248
+ issues.push({
1249
+ file,
1250
+ line: i + 1,
1251
+ type: "fab-not-optimal-position",
1252
+ severity: "info",
1253
+ message: "FAB not positioned at bottom-right (optimal thumb zone)",
1254
+ suggestion: "Position FAB at bottom-right for right-handed thumb reachability",
1255
+ snippet: line.trim().substring(0, 80),
1256
+ });
1257
+ }
1258
+ }
1259
+ }
1260
+ }
1261
+ return {
1262
+ name: "Thumb Zone Ergonomics",
1263
+ passed: true, // Info only
1264
+ blocking: false,
1265
+ issues,
1266
+ duration: Date.now() - startTime,
1267
+ };
1268
+ }
1269
+ /**
1270
+ * 20. Mobile Image Srcset
1271
+ * Check for responsive images with srcset for bandwidth optimization
1272
+ */
1273
+ async checkMobileImageSrcset() {
1274
+ const startTime = Date.now();
1275
+ const issues = [];
1276
+ const files = await this.getTsxFiles();
1277
+ let imagesWithoutSrcset = 0;
1278
+ let imagesWithSrcset = 0;
1279
+ for (const file of files) {
1280
+ const content = fs.readFileSync(file, "utf8");
1281
+ const lines = content.split("\n");
1282
+ for (let i = 0; i < lines.length; i++) {
1283
+ const line = lines[i];
1284
+ // Check for img elements (not Next.js Image which handles this)
1285
+ if (/<img\b/.test(line) && !/<Image\b/.test(line)) {
1286
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
1287
+ const hasSrcset = /srcSet|srcset/.test(contextBlock);
1288
+ const hasSizes = /sizes=/.test(contextBlock);
1289
+ if (!hasSrcset) {
1290
+ imagesWithoutSrcset++;
1291
+ // Only flag larger images (not icons)
1292
+ const isLikelyLargeImage = /hero|banner|product|card|thumbnail/i.test(contextBlock) ||
1293
+ /w-\[(?:[2-9]\d{2}|[1-9]\d{3})px\]|w-full/.test(contextBlock);
1294
+ if (isLikelyLargeImage) {
1295
+ issues.push({
1296
+ file,
1297
+ line: i + 1,
1298
+ type: "img-no-srcset",
1299
+ severity: "info",
1300
+ message: "Large <img> without srcset for responsive images",
1301
+ suggestion: "Add srcset for different screen sizes or use Next.js <Image> component",
1302
+ snippet: line.trim().substring(0, 80),
1303
+ });
1304
+ }
1305
+ }
1306
+ else {
1307
+ imagesWithSrcset++;
1308
+ if (!hasSizes) {
1309
+ issues.push({
1310
+ file,
1311
+ line: i + 1,
1312
+ type: "srcset-no-sizes",
1313
+ severity: "info",
1314
+ message: "<img> has srcset but missing sizes attribute",
1315
+ suggestion: "Add sizes attribute to help browser select correct image size",
1316
+ snippet: line.trim().substring(0, 80),
1317
+ });
1318
+ }
1319
+ }
1320
+ }
1321
+ // Check Next.js Image for sizes prop on responsive images
1322
+ if (/<Image\b/.test(line)) {
1323
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
1324
+ const hasFill = /fill/.test(contextBlock);
1325
+ const hasResponsiveWidth = /w-full|width.*%|responsive/i.test(contextBlock);
1326
+ const hasSizes = /sizes=/.test(contextBlock);
1327
+ if ((hasFill || hasResponsiveWidth) && !hasSizes) {
1328
+ issues.push({
1329
+ file,
1330
+ line: i + 1,
1331
+ type: "next-image-no-sizes",
1332
+ severity: "info",
1333
+ message: "Next.js Image with fill/responsive width but no sizes prop",
1334
+ suggestion: "Add sizes prop like sizes=\"(max-width: 768px) 100vw, 50vw\"",
1335
+ snippet: line.trim().substring(0, 80),
1336
+ });
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+ return {
1342
+ name: "Mobile Image Srcset",
1343
+ passed: true, // Info only
1344
+ blocking: false,
1345
+ issues,
1346
+ duration: Date.now() - startTime,
1347
+ };
1348
+ }
1349
+ /**
1350
+ * 21. Tap Target Size
1351
+ * Verify interactive elements meet minimum 44px tap target
1352
+ * Note: This check focuses on explicit small dimensions, not icon sizes
1353
+ */
1354
+ async checkTapTargetSize() {
1355
+ const startTime = Date.now();
1356
+ const issues = [];
1357
+ const files = await this.getTsxFiles();
1358
+ const MIN_TAP_SIZE = 44;
1359
+ for (const file of files) {
1360
+ const content = fs.readFileSync(file, "utf8");
1361
+ const lines = content.split("\n");
1362
+ for (let i = 0; i < lines.length; i++) {
1363
+ const line = lines[i];
1364
+ if (this.shouldSkipLine(lines, i))
1365
+ continue;
1366
+ // Only check IconButton specifically - regular Buttons typically have sufficient padding
1367
+ // We need to check the className prop on the IconButton, not children icons
1368
+ if (/<IconButton\b/i.test(line)) {
1369
+ // Get the IconButton props (up to closing > or multi-line until />)
1370
+ let propsText = line;
1371
+ let endIdx = i;
1372
+ // Find the end of the IconButton opening tag
1373
+ while (!/>/.test(lines[endIdx]) && endIdx < Math.min(i + 5, lines.length - 1)) {
1374
+ endIdx++;
1375
+ propsText += "\n" + lines[endIdx];
1376
+ }
1377
+ // Extract className from IconButton props only, not from child icons
1378
+ // Match className="..." on the IconButton, stopping before icon= or children
1379
+ const classNameMatch = propsText.match(/className=["']([^"']+)["']/);
1380
+ const buttonClasses = classNameMatch ? classNameMatch[1] : "";
1381
+ // Skip if button has padding classes or adequate sizes in className
1382
+ if (/\bp-\d|min-h-|min-w-|h-1[0-2]|h-[89]|w-1[0-2]|w-[89]/.test(buttonClasses)) {
1383
+ continue;
1384
+ }
1385
+ // Check for explicit small height in the button's className only
1386
+ const heightMatch = buttonClasses.match(/\bh-(\d+)\b/);
1387
+ if (heightMatch) {
1388
+ const height = parseInt(heightMatch[1]);
1389
+ // Tailwind h-N: h-5=20px, h-6=24px, h-7=28px, h-8=32px, h-9=36px, h-10=40px
1390
+ const pxHeight = height <= 12 ? height * 4 : height;
1391
+ if (pxHeight > 0 && pxHeight < 32) { // Only flag very small (< 32px)
1392
+ issues.push({
1393
+ file, line: i + 1, type: "tap-target-too-small", severity: "warning",
1394
+ message: `IconButton height ${pxHeight}px may be too small for touch`,
1395
+ suggestion: "Use h-10 (40px) or larger, or add padding for 44px tap target",
1396
+ snippet: line.trim().substring(0, 80),
1397
+ });
1398
+ }
1399
+ }
1400
+ }
1401
+ }
1402
+ }
1403
+ return { name: "Tap Target Size", passed: issues.filter((i) => i.severity === "error").length === 0, blocking: false, issues, duration: Date.now() - startTime };
1404
+ }
1405
+ /**
1406
+ * 22. Keyboard Dismiss Patterns
1407
+ */
1408
+ async checkKeyboardDismiss() {
1409
+ const startTime = Date.now();
1410
+ const issues = [];
1411
+ const files = await this.getTsxFiles();
1412
+ for (const file of files) {
1413
+ const content = fs.readFileSync(file, "utf8");
1414
+ const lines = content.split("\n");
1415
+ for (let i = 0; i < lines.length; i++) {
1416
+ const line = lines[i];
1417
+ if (/<form\b|<Form\b/i.test(line)) {
1418
+ const contextBlock = lines.slice(i, Math.min(i + 30, lines.length)).join("\n");
1419
+ const hasSubmit = /type=["']submit|onSubmit|handleSubmit/.test(contextBlock);
1420
+ const hasKeyDown = /onKeyDown|enterKeyHint/.test(contextBlock);
1421
+ if (!hasSubmit && !hasKeyDown) {
1422
+ issues.push({
1423
+ file, line: i + 1, type: "form-no-submit", severity: "info",
1424
+ message: "Form without submit action for keyboard dismiss",
1425
+ suggestion: "Add submit button or enterKeyHint for mobile keyboard",
1426
+ snippet: line.trim().substring(0, 80),
1427
+ });
1428
+ }
1429
+ }
1430
+ }
1431
+ }
1432
+ return { name: "Keyboard Dismiss", passed: true, blocking: false, issues, duration: Date.now() - startTime };
1433
+ }
1434
+ /**
1435
+ * 23. Navigation Depth
1436
+ */
1437
+ async checkNavigationDepth() {
1438
+ const startTime = Date.now();
1439
+ const issues = [];
1440
+ const files = await this.getTsxFiles();
1441
+ for (const file of files) {
1442
+ const content = fs.readFileSync(file, "utf8");
1443
+ const lines = content.split("\n");
1444
+ for (let i = 0; i < lines.length; i++) {
1445
+ const line = lines[i];
1446
+ if (/SubMenu|NestedMenu|submenu/i.test(line)) {
1447
+ const contextBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
1448
+ const nestedCount = (contextBlock.match(/SubMenu|NestedMenu|submenu/gi) || []).length;
1449
+ if (nestedCount > 2) {
1450
+ issues.push({
1451
+ file, line: i + 1, type: "deep-nested-menu", severity: "warning",
1452
+ message: `Deeply nested menu (${nestedCount} levels) - hard on mobile`,
1453
+ suggestion: "Flatten menu or use breadcrumb navigation",
1454
+ snippet: line.trim().substring(0, 80),
1455
+ });
1456
+ }
1457
+ }
1458
+ }
1459
+ }
1460
+ return { name: "Navigation Depth", passed: issues.filter((i) => i.severity === "error").length === 0, blocking: false, issues, duration: Date.now() - startTime };
1461
+ }
1462
+ /**
1463
+ * 24. Touch Scroll Momentum
1464
+ */
1465
+ async checkTouchScrollMomentum() {
1466
+ const startTime = Date.now();
1467
+ const issues = [];
1468
+ const files = await this.getTsxFiles();
1469
+ for (const file of files) {
1470
+ const content = fs.readFileSync(file, "utf8");
1471
+ const lines = content.split("\n");
1472
+ for (let i = 0; i < lines.length; i++) {
1473
+ const line = lines[i];
1474
+ if (/scroll-snap/.test(line)) {
1475
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
1476
+ if (!/scroll-smooth|scroll-behavior/.test(contextBlock)) {
1477
+ issues.push({
1478
+ file, line: i + 1, type: "scroll-snap-no-smooth", severity: "info",
1479
+ message: "Scroll snap without smooth behavior",
1480
+ suggestion: "Add scroll-smooth for smoother snap scrolling",
1481
+ snippet: line.trim().substring(0, 80),
1482
+ });
1483
+ }
1484
+ }
1485
+ }
1486
+ }
1487
+ return { name: "Touch Scroll Momentum", passed: true, blocking: false, issues, duration: Date.now() - startTime };
1488
+ }
1489
+ /**
1490
+ * 25. Mobile Form Chunking
1491
+ */
1492
+ async checkMobileFormChunking() {
1493
+ const startTime = Date.now();
1494
+ const issues = [];
1495
+ const files = await this.getTsxFiles();
1496
+ for (const file of files) {
1497
+ const content = fs.readFileSync(file, "utf8");
1498
+ const inputCount = (content.match(/<(?:input|Input|TextField|Select)\b/gi) || []).length;
1499
+ if (inputCount > 8 && !/step|wizard|FormSection/i.test(content)) {
1500
+ issues.push({
1501
+ file, line: 1, type: "long-form-no-chunking", severity: "info",
1502
+ message: `Form with ${inputCount} inputs - consider chunking into sections`,
1503
+ suggestion: "Break long forms into sections or multi-step wizard",
1504
+ });
1505
+ }
1506
+ }
1507
+ return { name: "Mobile Form Chunking", passed: true, blocking: false, issues, duration: Date.now() - startTime };
1508
+ }
1509
+ /**
1510
+ * 26. Pinch-to-Zoom Accessibility
1511
+ */
1512
+ async checkPinchToZoom() {
1513
+ const startTime = Date.now();
1514
+ const issues = [];
1515
+ const files = await this.getTsxFiles();
1516
+ for (const file of files) {
1517
+ if (!/layout|_app|_document|head/i.test(file))
1518
+ continue;
1519
+ const content = fs.readFileSync(file, "utf8");
1520
+ const lines = content.split("\n");
1521
+ for (let i = 0; i < lines.length; i++) {
1522
+ const line = lines[i];
1523
+ if (/user-scalable\s*=\s*["']?no|maximum-scale\s*=\s*["']?1(?:\.0)?["']?/.test(line)) {
1524
+ issues.push({
1525
+ file, line: i + 1, type: "zoom-disabled", severity: "error",
1526
+ message: "Pinch-to-zoom disabled - WCAG violation",
1527
+ suggestion: "Remove user-scalable=no to allow zoom",
1528
+ snippet: line.trim().substring(0, 80),
1529
+ });
1530
+ }
1531
+ }
1532
+ }
1533
+ return { name: "Pinch-to-Zoom", passed: issues.length === 0, blocking: true, issues, duration: Date.now() - startTime };
1534
+ }
1535
+ /**
1536
+ * 27. Text Selection
1537
+ */
1538
+ async checkTextSelection() {
1539
+ const startTime = Date.now();
1540
+ const issues = [];
1541
+ const files = await this.getTsxFiles();
1542
+ for (const file of files) {
1543
+ const content = fs.readFileSync(file, "utf8");
1544
+ const lines = content.split("\n");
1545
+ for (let i = 0; i < lines.length; i++) {
1546
+ const line = lines[i];
1547
+ if (/select-none/.test(line) && /<(?:p|span|div|article)[^>]*select-none/.test(line)) {
1548
+ issues.push({
1549
+ file, line: i + 1, type: "content-select-none", severity: "info",
1550
+ message: "select-none on content - users can't copy text",
1551
+ suggestion: "Only use select-none on buttons, not content",
1552
+ snippet: line.trim().substring(0, 80),
1553
+ });
1554
+ }
1555
+ }
1556
+ }
1557
+ return { name: "Text Selection", passed: true, blocking: false, issues, duration: Date.now() - startTime };
1558
+ }
1559
+ /**
1560
+ * 28. Mobile Date Pickers
1561
+ */
1562
+ async checkMobileDatePickers() {
1563
+ const startTime = Date.now();
1564
+ const issues = [];
1565
+ const files = await this.getTsxFiles();
1566
+ for (const file of files) {
1567
+ const content = fs.readFileSync(file, "utf8");
1568
+ const lines = content.split("\n");
1569
+ for (let i = 0; i < lines.length; i++) {
1570
+ const line = lines[i];
1571
+ if (/<(?:DatePicker|DateTimePicker|TimePicker)\b/i.test(line)) {
1572
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
1573
+ if (!/mobile|native|useNative/i.test(contextBlock)) {
1574
+ issues.push({
1575
+ file, line: i + 1, type: "custom-datepicker-no-mobile", severity: "info",
1576
+ message: "Custom date picker may not be optimal on mobile",
1577
+ suggestion: "Consider native date input on mobile",
1578
+ snippet: line.trim().substring(0, 80),
1579
+ });
1580
+ }
1581
+ }
1582
+ }
1583
+ }
1584
+ return { name: "Mobile Date Pickers", passed: true, blocking: false, issues, duration: Date.now() - startTime };
1585
+ }
1586
+ /**
1587
+ * 29. Network Status Indicator
1588
+ * Check for offline/online status handling
1589
+ */
1590
+ async checkNetworkStatusIndicator() {
1591
+ const startTime = Date.now();
1592
+ const issues = [];
1593
+ const files = await this.getTsxFiles();
1594
+ let hasOnlineCheck = false;
1595
+ let hasOfflineUI = false;
1596
+ let hasServiceWorker = false;
1597
+ for (const file of files) {
1598
+ const content = fs.readFileSync(file, "utf8");
1599
+ // Check for online/offline detection
1600
+ if (/navigator\.onLine|useOnline|useNetworkState|isOnline|isOffline/i.test(content)) {
1601
+ hasOnlineCheck = true;
1602
+ }
1603
+ // Check for offline UI
1604
+ if (/offline.*banner|offline.*indicator|no.*connection|network.*error/i.test(content)) {
1605
+ hasOfflineUI = true;
1606
+ }
1607
+ // Check for service worker
1608
+ if (/serviceWorker|workbox|sw\.js/i.test(content)) {
1609
+ hasServiceWorker = true;
1610
+ }
1611
+ const lines = content.split("\n");
1612
+ for (let i = 0; i < lines.length; i++) {
1613
+ const line = lines[i];
1614
+ // Check for fetch without offline handling
1615
+ if (/fetch\(|useMutation|useQuery/.test(line)) {
1616
+ const contextBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
1617
+ const hasNetworkError = /network.*error|offline|onLine|retry/i.test(contextBlock);
1618
+ // Only flag if it's a user-facing data fetch
1619
+ if (/useQuery|useMutation/.test(line) && !hasNetworkError && !hasOnlineCheck) {
1620
+ // This is tracked globally, not per-instance
1621
+ }
1622
+ }
1623
+ }
1624
+ }
1625
+ // Global check
1626
+ if (!hasOnlineCheck && !hasOfflineUI) {
1627
+ issues.push({
1628
+ file: "codebase-wide",
1629
+ line: 0,
1630
+ type: "no-offline-handling",
1631
+ severity: "info",
1632
+ message: "No offline/online status detection found",
1633
+ suggestion: "Add navigator.onLine check or useOnline hook to show offline indicator",
1634
+ });
1635
+ }
1636
+ if (hasOnlineCheck && !hasOfflineUI) {
1637
+ issues.push({
1638
+ file: "codebase-wide",
1639
+ line: 0,
1640
+ type: "online-check-no-ui",
1641
+ severity: "info",
1642
+ message: "Online status checked but no offline UI indicator found",
1643
+ suggestion: "Add visible banner or toast when user goes offline",
1644
+ });
1645
+ }
1646
+ return {
1647
+ name: "Network Status Indicator",
1648
+ passed: true,
1649
+ blocking: false,
1650
+ issues,
1651
+ duration: Date.now() - startTime,
1652
+ };
1653
+ }
1654
+ /**
1655
+ * 30. iOS Smart App Banner
1656
+ * Check for smart app banner meta tags
1657
+ */
1658
+ async checkIOSSmartAppBanner() {
1659
+ const startTime = Date.now();
1660
+ const issues = [];
1661
+ const files = await this.getTsxFiles();
1662
+ let hasSmartBanner = false;
1663
+ let hasAppId = false;
1664
+ for (const file of files) {
1665
+ if (!/layout|_app|_document|head/i.test(file))
1666
+ continue;
1667
+ const content = fs.readFileSync(file, "utf8");
1668
+ if (/apple-itunes-app/.test(content)) {
1669
+ hasSmartBanner = true;
1670
+ if (/app-id=/.test(content)) {
1671
+ hasAppId = true;
1672
+ }
1673
+ }
1674
+ // Check for custom app install prompts
1675
+ if (/installPrompt|beforeinstallprompt|AddToHomeScreen/i.test(content)) {
1676
+ hasSmartBanner = true;
1677
+ }
1678
+ }
1679
+ // This is optional - only suggest if it looks like they have a native app
1680
+ // We can't really detect this, so this is a very light check
1681
+ return {
1682
+ name: "iOS Smart App Banner",
1683
+ passed: true,
1684
+ blocking: false,
1685
+ issues,
1686
+ duration: Date.now() - startTime,
1687
+ };
1688
+ }
1689
+ /**
1690
+ * 31. Color Scheme Detection
1691
+ * Check for prefers-color-scheme handling
1692
+ */
1693
+ async checkColorSchemeDetection() {
1694
+ const startTime = Date.now();
1695
+ const issues = [];
1696
+ const files = await this.getTsxFiles();
1697
+ const cssFiles = await this.getCssFiles();
1698
+ let hasColorScheme = false;
1699
+ let hasDarkMode = false;
1700
+ let hasSystemPreference = false;
1701
+ // Check CSS
1702
+ for (const file of cssFiles) {
1703
+ const content = fs.readFileSync(file, "utf8");
1704
+ if (/prefers-color-scheme/.test(content)) {
1705
+ hasColorScheme = true;
1706
+ hasSystemPreference = true;
1707
+ }
1708
+ if (/dark:|\.dark\s|dark-mode|color-scheme:\s*dark/i.test(content)) {
1709
+ hasDarkMode = true;
1710
+ }
1711
+ }
1712
+ // Check TSX
1713
+ for (const file of files) {
1714
+ const content = fs.readFileSync(file, "utf8");
1715
+ if (/prefers-color-scheme|useColorScheme|useTheme|darkMode/i.test(content)) {
1716
+ hasColorScheme = true;
1717
+ }
1718
+ if (/dark:|theme.*dark|isDark|darkMode/i.test(content)) {
1719
+ hasDarkMode = true;
1720
+ }
1721
+ if (/system|matchMedia.*prefers-color-scheme/i.test(content)) {
1722
+ hasSystemPreference = true;
1723
+ }
1724
+ }
1725
+ if (hasDarkMode && !hasSystemPreference) {
1726
+ issues.push({
1727
+ file: "codebase-wide",
1728
+ line: 0,
1729
+ type: "dark-mode-no-system-pref",
1730
+ severity: "info",
1731
+ message: "Dark mode exists but may not respect system preference",
1732
+ suggestion: "Add @media (prefers-color-scheme: dark) or detect system preference",
1733
+ });
1734
+ }
1735
+ if (!hasDarkMode) {
1736
+ issues.push({
1737
+ file: "codebase-wide",
1738
+ line: 0,
1739
+ type: "no-dark-mode",
1740
+ severity: "info",
1741
+ message: "No dark mode support detected - important for OLED mobile screens",
1742
+ suggestion: "Add dark mode support using Tailwind dark: classes or CSS variables",
1743
+ });
1744
+ }
1745
+ return {
1746
+ name: "Color Scheme Detection",
1747
+ passed: true,
1748
+ blocking: false,
1749
+ issues,
1750
+ duration: Date.now() - startTime,
1751
+ };
1752
+ }
1753
+ /**
1754
+ * 32. Haptic Feedback
1755
+ * Check for vibration API on important actions
1756
+ */
1757
+ async checkHapticFeedback() {
1758
+ const startTime = Date.now();
1759
+ const issues = [];
1760
+ const files = await this.getTsxFiles();
1761
+ let hasVibration = false;
1762
+ let hasHapticFeedback = false;
1763
+ for (const file of files) {
1764
+ const content = fs.readFileSync(file, "utf8");
1765
+ if (/navigator\.vibrate|Vibration|haptic/i.test(content)) {
1766
+ hasVibration = true;
1767
+ }
1768
+ if (/useHaptic|hapticFeedback|taptic/i.test(content)) {
1769
+ hasHapticFeedback = true;
1770
+ }
1771
+ const lines = content.split("\n");
1772
+ for (let i = 0; i < lines.length; i++) {
1773
+ const line = lines[i];
1774
+ // Check for critical actions that might benefit from haptic
1775
+ if (/handleDelete|handleSubmit|handlePurchase|handlePayment|onError/i.test(line)) {
1776
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
1777
+ if (!hasVibration && !hasHapticFeedback) {
1778
+ // Don't flag individual instances, just track globally
1779
+ }
1780
+ }
1781
+ }
1782
+ }
1783
+ // Optional feature - just informational
1784
+ if (!hasVibration && !hasHapticFeedback) {
1785
+ issues.push({
1786
+ file: "codebase-wide",
1787
+ line: 0,
1788
+ type: "no-haptic-feedback",
1789
+ severity: "info",
1790
+ message: "No haptic feedback detected - consider for critical actions",
1791
+ suggestion: "Add navigator.vibrate([10]) for subtle feedback on important actions",
1792
+ });
1793
+ }
1794
+ return {
1795
+ name: "Haptic Feedback",
1796
+ passed: true,
1797
+ blocking: false,
1798
+ issues,
1799
+ duration: Date.now() - startTime,
1800
+ };
1801
+ }
1802
+ /**
1803
+ * 33. Font Loading Strategy
1804
+ * Check for font-display and system font fallbacks
1805
+ */
1806
+ async checkFontLoadingStrategy() {
1807
+ const startTime = Date.now();
1808
+ const issues = [];
1809
+ const cssFiles = await this.getCssFiles();
1810
+ const files = await this.getTsxFiles();
1811
+ let hasFontDisplay = false;
1812
+ let hasSystemFallback = false;
1813
+ let hasFontPreload = false;
1814
+ // Check CSS
1815
+ for (const file of cssFiles) {
1816
+ const content = fs.readFileSync(file, "utf8");
1817
+ const lines = content.split("\n");
1818
+ if (/font-display:\s*(?:swap|optional|fallback)/.test(content)) {
1819
+ hasFontDisplay = true;
1820
+ }
1821
+ // Check for system font fallback
1822
+ if (/system-ui|-apple-system|BlinkMacSystemFont|Segoe UI/.test(content)) {
1823
+ hasSystemFallback = true;
1824
+ }
1825
+ for (let i = 0; i < lines.length; i++) {
1826
+ const line = lines[i];
1827
+ // Check @font-face without font-display
1828
+ if (/@font-face/.test(line)) {
1829
+ const fontBlock = content.substring(content.indexOf(line), content.indexOf("}", content.indexOf(line)) + 1);
1830
+ if (!/font-display/.test(fontBlock)) {
1831
+ issues.push({
1832
+ file,
1833
+ line: i + 1,
1834
+ type: "font-face-no-display",
1835
+ severity: "warning",
1836
+ message: "@font-face without font-display causes FOIT on mobile",
1837
+ suggestion: "Add font-display: swap to show fallback font while loading",
1838
+ snippet: line.trim().substring(0, 80),
1839
+ });
1840
+ }
1841
+ }
1842
+ }
1843
+ }
1844
+ // Check for font preload in layout files
1845
+ for (const file of files) {
1846
+ if (!/layout|_app|_document|head/i.test(file))
1847
+ continue;
1848
+ const content = fs.readFileSync(file, "utf8");
1849
+ if (/rel=["']preload["'][^>]*as=["']font/.test(content) ||
1850
+ /next\/font|@next\/font/.test(content)) {
1851
+ hasFontPreload = true;
1852
+ }
1853
+ }
1854
+ if (!hasFontDisplay && !hasFontPreload) {
1855
+ issues.push({
1856
+ file: "codebase-wide",
1857
+ line: 0,
1858
+ type: "no-font-loading-strategy",
1859
+ severity: "info",
1860
+ message: "No font loading optimization detected",
1861
+ suggestion: "Use next/font or add font-display: swap and preload critical fonts",
1862
+ });
1863
+ }
1864
+ if (!hasSystemFallback) {
1865
+ issues.push({
1866
+ file: "codebase-wide",
1867
+ line: 0,
1868
+ type: "no-system-font-fallback",
1869
+ severity: "info",
1870
+ message: "No system font fallback in font stack",
1871
+ suggestion: "Add system-ui, -apple-system as fallback for faster initial render",
1872
+ });
1873
+ }
1874
+ return {
1875
+ name: "Font Loading Strategy",
1876
+ passed: issues.filter((i) => i.severity === "error").length === 0,
1877
+ blocking: false,
1878
+ issues,
1879
+ duration: Date.now() - startTime,
1880
+ };
1881
+ }
1882
+ /**
1883
+ * 34. Passive Event Listeners
1884
+ * Check for passive scroll/touch listeners for better performance
1885
+ */
1886
+ async checkPassiveEventListeners() {
1887
+ const startTime = Date.now();
1888
+ const issues = [];
1889
+ const files = await this.getTsxFiles();
1890
+ for (const file of files) {
1891
+ const content = fs.readFileSync(file, "utf8");
1892
+ const lines = content.split("\n");
1893
+ for (let i = 0; i < lines.length; i++) {
1894
+ const line = lines[i];
1895
+ // Check for addEventListener with scroll/touch/wheel
1896
+ if (/addEventListener\s*\(\s*["'](?:scroll|touchstart|touchmove|wheel)["']/.test(line)) {
1897
+ const contextBlock = lines.slice(i, Math.min(i + 3, lines.length)).join("\n");
1898
+ if (!/passive:\s*true|\{\s*passive/.test(contextBlock)) {
1899
+ issues.push({
1900
+ file,
1901
+ line: i + 1,
1902
+ type: "scroll-listener-not-passive",
1903
+ severity: "warning",
1904
+ message: "Scroll/touch listener without passive option hurts mobile scroll performance",
1905
+ suggestion: "Add { passive: true } or use React's built-in passive handling",
1906
+ snippet: line.trim().substring(0, 80),
1907
+ });
1908
+ }
1909
+ }
1910
+ // Check for onScroll handlers that might block
1911
+ if (/onScroll=\{[^}]*preventDefault/.test(line)) {
1912
+ issues.push({
1913
+ file,
1914
+ line: i + 1,
1915
+ type: "scroll-prevent-default",
1916
+ severity: "warning",
1917
+ message: "preventDefault in scroll handler blocks smooth scrolling",
1918
+ suggestion: "Avoid preventDefault in scroll handlers if possible",
1919
+ snippet: line.trim().substring(0, 80),
1920
+ });
1921
+ }
1922
+ // Check for wheel handler without passive
1923
+ if (/onWheel=/.test(line)) {
1924
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
1925
+ if (/preventDefault/.test(contextBlock)) {
1926
+ issues.push({
1927
+ file,
1928
+ line: i + 1,
1929
+ type: "wheel-prevent-default",
1930
+ severity: "info",
1931
+ message: "Wheel handler with preventDefault may affect trackpad scrolling",
1932
+ suggestion: "Consider using CSS touch-action instead of JS prevention",
1933
+ snippet: line.trim().substring(0, 80),
1934
+ });
1935
+ }
1936
+ }
1937
+ }
1938
+ }
1939
+ return {
1940
+ name: "Passive Event Listeners",
1941
+ passed: issues.filter((i) => i.severity === "error").length === 0,
1942
+ blocking: false,
1943
+ issues,
1944
+ duration: Date.now() - startTime,
1945
+ };
1946
+ }
1947
+ /**
1948
+ * 35. Viewport Resize Handling
1949
+ * Check for keyboard resize handling on Android
1950
+ */
1951
+ async checkViewportResizeHandling() {
1952
+ const startTime = Date.now();
1953
+ const issues = [];
1954
+ const files = await this.getTsxFiles();
1955
+ let hasVisualViewport = false;
1956
+ let hasResizeHandler = false;
1957
+ for (const file of files) {
1958
+ const content = fs.readFileSync(file, "utf8");
1959
+ if (/visualViewport/.test(content)) {
1960
+ hasVisualViewport = true;
1961
+ }
1962
+ if (/resize.*keyboard|keyboard.*resize|innerHeight/i.test(content)) {
1963
+ hasResizeHandler = true;
1964
+ }
1965
+ const lines = content.split("\n");
1966
+ for (let i = 0; i < lines.length; i++) {
1967
+ const line = lines[i];
1968
+ // Check for fixed bottom elements that might be hidden by keyboard
1969
+ if (/fixed.*bottom-0|sticky.*bottom-0/.test(line)) {
1970
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
1971
+ // Check if it's a form or input-related
1972
+ const hasInputNearby = /input|Input|TextField|form|Form/i.test(contextBlock);
1973
+ if (hasInputNearby && !hasVisualViewport && !hasResizeHandler) {
1974
+ issues.push({
1975
+ file,
1976
+ line: i + 1,
1977
+ type: "fixed-bottom-keyboard-issue",
1978
+ severity: "info",
1979
+ message: "Fixed bottom element near inputs - may be hidden by mobile keyboard",
1980
+ suggestion: "Use visualViewport API to adjust position when keyboard opens",
1981
+ snippet: line.trim().substring(0, 80),
1982
+ });
1983
+ }
1984
+ }
1985
+ }
1986
+ }
1987
+ // Check for apps with forms but no keyboard handling
1988
+ let hasFormsWithFixedElements = false;
1989
+ for (const file of files) {
1990
+ const content = fs.readFileSync(file, "utf8");
1991
+ if (/<form|<Form/i.test(content) && /fixed.*bottom|sticky.*bottom/.test(content)) {
1992
+ hasFormsWithFixedElements = true;
1993
+ break;
1994
+ }
1995
+ }
1996
+ if (hasFormsWithFixedElements && !hasVisualViewport) {
1997
+ issues.push({
1998
+ file: "codebase-wide",
1999
+ line: 0,
2000
+ type: "no-visual-viewport-api",
2001
+ severity: "info",
2002
+ message: "Forms with fixed elements but no visualViewport handling",
2003
+ suggestion: "Use window.visualViewport for accurate viewport size with mobile keyboard",
2004
+ });
2005
+ }
2006
+ return {
2007
+ name: "Viewport Resize Handling",
2008
+ passed: true,
2009
+ blocking: false,
2010
+ issues,
2011
+ duration: Date.now() - startTime,
2012
+ };
2013
+ }
2014
+ /**
2015
+ * 36. Network-Aware Loading
2016
+ * Check for connection-aware image/data loading
2017
+ */
2018
+ async checkNetworkAwareLoading() {
2019
+ const startTime = Date.now();
2020
+ const issues = [];
2021
+ const files = await this.getTsxFiles();
2022
+ let hasNetworkInfo = false;
2023
+ let hasLazyLoading = false;
2024
+ let hasProgressiveImages = false;
2025
+ for (const file of files) {
2026
+ const content = fs.readFileSync(file, "utf8");
2027
+ // Check for Network Information API
2028
+ if (/navigator\.connection|effectiveType|saveData|NetworkInformation/i.test(content)) {
2029
+ hasNetworkInfo = true;
2030
+ }
2031
+ // Check for lazy loading
2032
+ if (/loading=["']lazy|LazyLoad|lazy.*load|IntersectionObserver/i.test(content)) {
2033
+ hasLazyLoading = true;
2034
+ }
2035
+ // Check for progressive images
2036
+ if (/blur.*placeholder|placeholder.*blur|lqip|progressive/i.test(content)) {
2037
+ hasProgressiveImages = true;
2038
+ }
2039
+ const lines = content.split("\n");
2040
+ for (let i = 0; i < lines.length; i++) {
2041
+ const line = lines[i];
2042
+ // Check for large data fetches without pagination
2043
+ if (/useQuery|useSWR|fetch/.test(line)) {
2044
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
2045
+ // Check for limit/pagination
2046
+ const hasPagination = /limit|page|offset|cursor|take|first|last/i.test(contextBlock);
2047
+ const hasInfinite = /infinite|loadMore|fetchMore/i.test(contextBlock);
2048
+ // Only flag if fetching "all" without pagination
2049
+ if (/getAll|fetchAll|loadAll/i.test(line) && !hasPagination && !hasInfinite) {
2050
+ issues.push({
2051
+ file,
2052
+ line: i + 1,
2053
+ type: "fetch-all-no-pagination",
2054
+ severity: "info",
2055
+ message: "Fetching all data without pagination - heavy on mobile networks",
2056
+ suggestion: "Add pagination or infinite scroll for large data sets",
2057
+ snippet: line.trim().substring(0, 80),
2058
+ });
2059
+ }
2060
+ }
2061
+ }
2062
+ }
2063
+ if (!hasLazyLoading) {
2064
+ issues.push({
2065
+ file: "codebase-wide",
2066
+ line: 0,
2067
+ type: "no-lazy-loading",
2068
+ severity: "info",
2069
+ message: "No image lazy loading detected",
2070
+ suggestion: "Add loading=\"lazy\" to below-fold images for faster initial load",
2071
+ });
2072
+ }
2073
+ if (!hasProgressiveImages) {
2074
+ issues.push({
2075
+ file: "codebase-wide",
2076
+ line: 0,
2077
+ type: "no-progressive-images",
2078
+ severity: "info",
2079
+ message: "No progressive/blur-up image loading detected",
2080
+ suggestion: "Use Next.js Image placeholder=\"blur\" for better perceived performance",
2081
+ });
2082
+ }
2083
+ return {
2084
+ name: "Network-Aware Loading",
2085
+ passed: true,
2086
+ blocking: false,
2087
+ issues,
2088
+ duration: Date.now() - startTime,
2089
+ };
2090
+ }
2091
+ /**
2092
+ * Run all checks
2093
+ */
2094
+ async runAll(filter) {
2095
+ const startTime = Date.now();
2096
+ const checks = [];
2097
+ const checkMap = {
2098
+ safearea: () => this.checkSafeAreaInsets(),
2099
+ inputs: () => this.checkInputTypes(),
2100
+ fonts: () => this.checkFontSizeMinimum(),
2101
+ overflow: () => this.checkHorizontalOverflow(),
2102
+ viewport: () => this.checkViewportUnits(),
2103
+ hover: () => this.checkHoverMediaQuery(),
2104
+ labels: () => this.checkFormLabels(),
2105
+ sticky: () => this.checkStickyPosition(),
2106
+ orientation: () => this.checkOrientationSupport(),
2107
+ touch: () => this.checkTouchAction(),
2108
+ focus: () => this.checkMobileMenuFocus(),
2109
+ bottomnav: () => this.checkBottomNavigation(),
2110
+ pulltorefresh: () => this.checkPullToRefresh(),
2111
+ tables: () => this.checkMobileTablePatterns(),
2112
+ modals: () => this.checkMobileModalSizing(),
2113
+ overscroll: () => this.checkOverscrollBehavior(),
2114
+ pwa: () => this.checkPWAMetaTags(),
2115
+ autofill: () => this.checkMobileAutofillStyling(),
2116
+ thumbzone: () => this.checkThumbZoneErgonomics(),
2117
+ srcset: () => this.checkMobileImageSrcset(),
2118
+ taptarget: () => this.checkTapTargetSize(),
2119
+ keyboard: () => this.checkKeyboardDismiss(),
2120
+ navdepth: () => this.checkNavigationDepth(),
2121
+ momentum: () => this.checkTouchScrollMomentum(),
2122
+ formchunk: () => this.checkMobileFormChunking(),
2123
+ zoom: () => this.checkPinchToZoom(),
2124
+ textselect: () => this.checkTextSelection(),
2125
+ datepicker: () => this.checkMobileDatePickers(),
2126
+ network: () => this.checkNetworkStatusIndicator(),
2127
+ smartbanner: () => this.checkIOSSmartAppBanner(),
2128
+ colorscheme: () => this.checkColorSchemeDetection(),
2129
+ haptic: () => this.checkHapticFeedback(),
2130
+ fontload: () => this.checkFontLoadingStrategy(),
2131
+ passive: () => this.checkPassiveEventListeners(),
2132
+ vpresize: () => this.checkViewportResizeHandling(),
2133
+ networkaware: () => this.checkNetworkAwareLoading(),
2134
+ };
2135
+ if (filter && checkMap[filter]) {
2136
+ checks.push(await checkMap[filter]());
2137
+ }
2138
+ else {
2139
+ for (const check of Object.values(checkMap)) {
2140
+ checks.push(await check());
2141
+ }
2142
+ }
2143
+ const errors = checks.reduce((sum, c) => sum + c.issues.filter((i) => i.severity === "error").length, 0);
2144
+ const warnings = checks.reduce((sum, c) => sum + c.issues.filter((i) => i.severity === "warning").length, 0);
2145
+ return {
2146
+ module: "UI Mobile UX",
2147
+ passed: errors === 0,
2148
+ totalDuration: Date.now() - startTime,
2149
+ checks,
2150
+ summary: {
2151
+ total: checks.length,
2152
+ passed: checks.filter((c) => c.passed).length,
2153
+ failed: checks.filter((c) => !c.passed).length,
2154
+ errors,
2155
+ warnings,
2156
+ },
2157
+ };
2158
+ }
2159
+ }
2160
+ exports.UIMobileUXModule = UIMobileUXModule;
2161
+ // CLI ENTRY POINT
2162
+ async function main() {
2163
+ const args = process.argv.slice(2);
2164
+ const filter = args.find((a) => !a.startsWith("-"));
2165
+ const verbose = args.includes("--verbose") || args.includes("-v");
2166
+ const module = new UIMobileUXModule({ verbose });
2167
+ const result = await module.runAll(filter);
2168
+ console.log((0, console_chars_1.createDivider)(80, "heavy"));
2169
+ console.log(`${console_chars_1.emoji.mobile} UI MOBILE UX CHECK`);
2170
+ console.log((0, console_chars_1.createDivider)(80, "heavy"));
2171
+ console.log();
2172
+ for (const check of result.checks) {
2173
+ const icon = check.passed
2174
+ ? console_chars_1.emoji.success
2175
+ : check.issues.some((i) => i.severity === "error")
2176
+ ? console_chars_1.emoji.error
2177
+ : console_chars_1.emoji.warning;
2178
+ console.log(`${icon} ${check.name} (${check.duration}ms)`);
2179
+ if (check.issues.length > 0 && (verbose || check.issues.some((i) => i.severity !== "info"))) {
2180
+ for (const issue of check.issues.slice(0, verbose ? 100 : 5)) {
2181
+ const sevIcon = issue.severity === "error"
2182
+ ? console_chars_1.emoji.error
2183
+ : issue.severity === "warning"
2184
+ ? console_chars_1.emoji.warning
2185
+ : console_chars_1.emoji.info;
2186
+ const relPath = path.relative(process.cwd(), issue.file).replace(/\\/g, "/");
2187
+ console.log(` ${sevIcon} ${relPath}:${issue.line}`);
2188
+ console.log(` ${issue.message}`);
2189
+ console.log(` ${console_chars_1.emoji.hint} ${issue.suggestion}`);
2190
+ }
2191
+ if (!verbose && check.issues.length > 5) {
2192
+ console.log(` ... and ${check.issues.length - 5} more issues`);
2193
+ }
2194
+ }
2195
+ console.log();
2196
+ }
2197
+ console.log((0, console_chars_1.createDivider)(80, "light"));
2198
+ console.log(`Total: ${result.summary.total} checks | ${console_chars_1.emoji.error} ${result.summary.errors} errors | ${console_chars_1.emoji.warning} ${result.summary.warnings} warnings`);
2199
+ console.log(`Duration: ${(result.totalDuration / 1000).toFixed(2)}s`);
2200
+ console.log((0, console_chars_1.createDivider)(80, "heavy"));
2201
+ if (result.summary.errors > 0) {
2202
+ console.log(`\n${console_chars_1.emoji.error} MOBILE UX CHECK FAILED\n`);
2203
+ process.exit(1);
2204
+ }
2205
+ else {
2206
+ console.log(`\n${console_chars_1.emoji.success} MOBILE UX CHECK PASSED\n`);
2207
+ process.exit(0);
2208
+ }
2209
+ }
2210
+ if (require.main === module)
2211
+ main();
2212
+ //# sourceMappingURL=ui-mobile-ux.js.map