@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,2474 @@
1
+ #!/usr/bin/env tsx
2
+ "use strict";
3
+ /**
4
+ * UI Interactive States Preflight (BLOCKING)
5
+ *
6
+ * Google/Meta/X-level interactive state consistency.
7
+ * Ensures hover, active, focus, and disabled states are consistent across all components.
8
+ *
9
+ * Checks:
10
+ * 1. Hover state consistency - Same hover tokens across similar components
11
+ * 2. Active/pressed state coverage - All buttons have active states
12
+ * 3. Focus ring consistency - Same focus style across components
13
+ * 4. Disabled state styling - Consistent disabled appearance
14
+ * 5. Cursor consistency - Correct cursor for each state
15
+ *
16
+ * Usage:
17
+ * pnpm preflight:ui-interactive-states # All checks
18
+ * pnpm preflight:ui-interactive-states hover # Hover states only
19
+ * pnpm preflight:ui-interactive-states active # Active states only
20
+ * pnpm preflight:ui-interactive-states focus # Focus states only
21
+ * pnpm preflight:ui-interactive-states disabled # Disabled states only
22
+ * pnpm preflight:ui-interactive-states cursor # Cursor consistency only
23
+ */
24
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ var desc = Object.getOwnPropertyDescriptor(m, k);
27
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
28
+ desc = { enumerable: true, get: function() { return m[k]; } };
29
+ }
30
+ Object.defineProperty(o, k2, desc);
31
+ }) : (function(o, m, k, k2) {
32
+ if (k2 === undefined) k2 = k;
33
+ o[k2] = m[k];
34
+ }));
35
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
36
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
37
+ }) : function(o, v) {
38
+ o["default"] = v;
39
+ });
40
+ var __importStar = (this && this.__importStar) || (function () {
41
+ var ownKeys = function(o) {
42
+ ownKeys = Object.getOwnPropertyNames || function (o) {
43
+ var ar = [];
44
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
45
+ return ar;
46
+ };
47
+ return ownKeys(o);
48
+ };
49
+ return function (mod) {
50
+ if (mod && mod.__esModule) return mod;
51
+ var result = {};
52
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
53
+ __setModuleDefault(result, mod);
54
+ return result;
55
+ };
56
+ })();
57
+ Object.defineProperty(exports, "__esModule", { value: true });
58
+ exports.UIInteractiveStatesModule = exports.STANDARD_HOVER_TOKENS = exports.STANDARD_FOCUS_STYLES = void 0;
59
+ const fs = __importStar(require("fs"));
60
+ const path = __importStar(require("path"));
61
+ const console_chars_1 = require("../../utils/console-chars");
62
+ const file_cache_1 = require("../../shared/file-cache");
63
+ const glob_patterns_1 = require("../../shared/glob-patterns");
64
+ const universal_progress_reporter_1 = require("../system/universal-progress-reporter");
65
+ const concurrency_config_1 = require("../../shared/concurrency-config");
66
+ const EXCLUDED = [...glob_patterns_1.STANDARD_EXCLUDES, "**/*.test.tsx", "**/*.spec.tsx", "**/*.stories.tsx"];
67
+ // INTERACTIVE STATE STANDARDS
68
+ /**
69
+ * Standard hover tokens - should be consistent across components
70
+ */
71
+ const STANDARD_HOVER_TOKENS = {
72
+ // Background hovers
73
+ "bg-tertiary": "var(--bg-tertiary)",
74
+ "overlay-light": "var(--overlay-light)",
75
+ "overlay-medium": "var(--overlay-medium)",
76
+ "primary-hover": "var(--primary-hover)",
77
+ // Tailwind equivalents
78
+ "bg-tertiary-tw": "hover:bg-[var(--bg-tertiary)]",
79
+ "overlay-light-tw": "hover:bg-[var(--overlay-light)]",
80
+ };
81
+ exports.STANDARD_HOVER_TOKENS = STANDARD_HOVER_TOKENS;
82
+ /**
83
+ * Standard focus ring styles
84
+ */
85
+ const STANDARD_FOCUS_STYLES = {
86
+ ring: "focus-visible:ring-2",
87
+ ringColor: "focus-visible:ring-primary",
88
+ outline: "focus-visible:outline-none",
89
+ };
90
+ exports.STANDARD_FOCUS_STYLES = STANDARD_FOCUS_STYLES;
91
+ /**
92
+ * Interactive elements that should have hover states
93
+ */
94
+ const INTERACTIVE_ELEMENTS = [
95
+ "Button",
96
+ "IconButton",
97
+ "Link",
98
+ "a",
99
+ "MenuItem",
100
+ "ListItemButton",
101
+ "Tab",
102
+ "Chip",
103
+ "Card", // When clickable
104
+ ];
105
+ // Get concurrency from shared config (respects PREFLIGHT_CONCURRENCY env var)
106
+ const concurrencyConfig = (0, concurrency_config_1.getConcurrencyConfig)();
107
+ class UIInteractiveStatesModule {
108
+ verbose;
109
+ parallel = false;
110
+ constructor(options = {}) {
111
+ this.verbose = options.verbose || false;
112
+ this.parallel = options.parallel || concurrencyConfig.parallel;
113
+ }
114
+ async getTsxFiles() {
115
+ return file_cache_1.fileCache.getAppAndComponentsTSX();
116
+ }
117
+ /**
118
+ * 1. Hover State Consistency
119
+ * Ensure consistent hover background tokens
120
+ */
121
+ async checkHoverStateConsistency() {
122
+ const startTime = Date.now();
123
+ const issues = [];
124
+ const files = await this.getTsxFiles();
125
+ // Track hover patterns used across codebase
126
+ const hoverPatterns = new Map();
127
+ for (const file of files) {
128
+ const content = fs.readFileSync(file, "utf8");
129
+ const lines = content.split("\n");
130
+ for (let i = 0; i < lines.length; i++) {
131
+ const line = lines[i];
132
+ // Skip lines with preflight-ignore comment (current line or up to 5 lines before)
133
+ const contextLines = lines.slice(Math.max(0, i - 5), i + 1).join("\n");
134
+ if (contextLines.includes("preflight-ignore")) {
135
+ continue;
136
+ }
137
+ // Track hover:bg-* patterns
138
+ const hoverBgMatch = line.match(/hover:bg-\[([^\]]+)\]/);
139
+ if (hoverBgMatch) {
140
+ const pattern = hoverBgMatch[1];
141
+ const existing = hoverPatterns.get(pattern) || { count: 0, files: [] };
142
+ existing.count++;
143
+ if (!existing.files.includes(file))
144
+ existing.files.push(file);
145
+ hoverPatterns.set(pattern, existing);
146
+ }
147
+ // Check for hardcoded hover colors (not using tokens)
148
+ if (/hover:bg-(?:gray|slate|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d{2,3}/.test(line)) {
149
+ issues.push({
150
+ file,
151
+ line: i + 1,
152
+ type: "hardcoded-hover-color",
153
+ severity: "warning",
154
+ message: "Hardcoded Tailwind color for hover state",
155
+ suggestion: "Use hover:bg-[var(--bg-tertiary)] or hover:bg-[var(--overlay-light)]",
156
+ snippet: line.trim().substring(0, 80),
157
+ });
158
+ }
159
+ // Check for inline hover styles (should use Tailwind)
160
+ if (/:hover/.test(line) && /style=/.test(line)) {
161
+ issues.push({
162
+ file,
163
+ line: i + 1,
164
+ type: "inline-hover-style",
165
+ severity: "warning",
166
+ message: "Inline CSS hover style (prefer Tailwind)",
167
+ suggestion: "Use Tailwind hover: classes instead of inline :hover styles",
168
+ snippet: line.trim().substring(0, 80),
169
+ });
170
+ }
171
+ }
172
+ }
173
+ // Report inconsistent hover patterns
174
+ if (hoverPatterns.size > 3) {
175
+ const sorted = [...hoverPatterns.entries()].sort((a, b) => b[1].count - a[1].count);
176
+ const summary = sorted
177
+ .slice(0, 5)
178
+ .map(([p, d]) => `${p} (${d.count}x)`)
179
+ .join(", ");
180
+ issues.push({
181
+ file: "codebase-wide",
182
+ line: 0,
183
+ type: "hover-pattern-inconsistency",
184
+ severity: "info",
185
+ message: `${hoverPatterns.size} different hover background patterns detected`,
186
+ suggestion: `Standardize on 2-3 hover tokens. Top patterns: ${summary}`,
187
+ });
188
+ }
189
+ return {
190
+ name: "Hover State Consistency",
191
+ passed: issues.filter((i) => i.severity === "error").length === 0,
192
+ blocking: false,
193
+ issues,
194
+ duration: Date.now() - startTime,
195
+ };
196
+ }
197
+ /**
198
+ * 2. Active/Pressed State Coverage
199
+ * Ensure interactive elements have active states
200
+ */
201
+ async checkActiveStateCoverage() {
202
+ const startTime = Date.now();
203
+ const issues = [];
204
+ const files = await this.getTsxFiles();
205
+ for (const file of files) {
206
+ const content = fs.readFileSync(file, "utf8");
207
+ const lines = content.split("\n");
208
+ for (let i = 0; i < lines.length; i++) {
209
+ const line = lines[i];
210
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
211
+ // Check buttons with hover but no active state
212
+ if (/<Button/.test(line) || /className=["'][^"']*hover:/.test(line)) {
213
+ const hasHover = /hover:/.test(contextBlock);
214
+ const hasActive = /active:|:active/.test(contextBlock);
215
+ // Only flag if it has custom hover but no active
216
+ if (hasHover && !hasActive && /className=/.test(line)) {
217
+ const hasBuiltInButton = /<Button|<IconButton|<LoadingButton|<AnimatedButton/.test(line);
218
+ if (!hasBuiltInButton) {
219
+ issues.push({
220
+ file,
221
+ line: i + 1,
222
+ type: "hover-no-active",
223
+ severity: "info",
224
+ message: "Interactive element has hover state but no active state",
225
+ suggestion: "Add active:scale-[0.98] or active:bg-[var(--overlay-medium)] for feedback",
226
+ snippet: line.trim().substring(0, 80),
227
+ });
228
+ }
229
+ }
230
+ }
231
+ // Check custom clickable elements
232
+ if (/<div[^>]*onClick|<Box[^>]*onClick/.test(line)) {
233
+ const hasActive = /active:|:active/.test(contextBlock);
234
+ const hasCursor = /cursor-pointer/.test(contextBlock);
235
+ if (hasCursor && !hasActive) {
236
+ issues.push({
237
+ file,
238
+ line: i + 1,
239
+ type: "clickable-no-active",
240
+ severity: "info",
241
+ message: "Clickable element without active/pressed state",
242
+ suggestion: "Add active:scale-[0.98] or active:opacity-90 for tactile feedback",
243
+ snippet: line.trim().substring(0, 80),
244
+ });
245
+ }
246
+ }
247
+ }
248
+ }
249
+ return {
250
+ name: "Active State Coverage",
251
+ passed: true, // Info only
252
+ blocking: false,
253
+ issues,
254
+ duration: Date.now() - startTime,
255
+ };
256
+ }
257
+ /**
258
+ * 3. Focus Ring Consistency
259
+ * Ensure focus states use consistent styling
260
+ */
261
+ async checkFocusRingConsistency() {
262
+ const startTime = Date.now();
263
+ const issues = [];
264
+ const files = await this.getTsxFiles();
265
+ // Track focus ring patterns
266
+ const focusPatterns = new Map();
267
+ for (const file of files) {
268
+ const content = fs.readFileSync(file, "utf8");
269
+ const lines = content.split("\n");
270
+ for (let i = 0; i < lines.length; i++) {
271
+ const line = lines[i];
272
+ // Track focus ring color patterns
273
+ const focusRingMatch = line.match(/focus(?:-visible)?:ring-(\w+)/);
274
+ if (focusRingMatch) {
275
+ const color = focusRingMatch[1];
276
+ focusPatterns.set(color, (focusPatterns.get(color) || 0) + 1);
277
+ }
278
+ // Check for focus:outline-none without ring
279
+ if (/focus:outline-none/.test(line) && !/focus(?:-visible)?:ring/.test(line)) {
280
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
281
+ const hasOtherFocusIndicator = /focus:border|focus:shadow|focus:bg/.test(contextBlock);
282
+ if (!hasOtherFocusIndicator) {
283
+ issues.push({
284
+ file,
285
+ line: i + 1,
286
+ type: "outline-none-no-alternative",
287
+ severity: "warning",
288
+ message: "focus:outline-none without alternative focus indicator",
289
+ suggestion: "Add focus-visible:ring-2 focus-visible:ring-primary",
290
+ snippet: line.trim().substring(0, 80),
291
+ });
292
+ }
293
+ }
294
+ // Check for focus: instead of focus-visible: (accessibility issue)
295
+ if (/\bfocus:(?!visible)/.test(line) && /ring|outline|border|shadow/.test(line)) {
296
+ // Skip if focus-visible is also present
297
+ if (!/focus-visible:/.test(line)) {
298
+ issues.push({
299
+ file,
300
+ line: i + 1,
301
+ type: "focus-instead-of-focus-visible",
302
+ severity: "info",
303
+ message: "Using focus: instead of focus-visible: for visual indicators",
304
+ suggestion: "Use focus-visible: to only show focus ring for keyboard navigation",
305
+ snippet: line.trim().substring(0, 80),
306
+ });
307
+ }
308
+ }
309
+ }
310
+ }
311
+ // Report focus ring color inconsistencies
312
+ if (focusPatterns.size > 2) {
313
+ const sorted = [...focusPatterns.entries()].sort((a, b) => b[1] - a[1]);
314
+ issues.push({
315
+ file: "codebase-wide",
316
+ line: 0,
317
+ type: "focus-ring-color-inconsistency",
318
+ severity: "info",
319
+ message: `${focusPatterns.size} different focus ring colors detected`,
320
+ suggestion: `Standardize on primary ring color. Usage: ${sorted.map(([c, n]) => `${c}(${n})`).join(", ")}`,
321
+ });
322
+ }
323
+ return {
324
+ name: "Focus Ring Consistency",
325
+ passed: issues.filter((i) => i.severity === "error").length === 0,
326
+ blocking: false,
327
+ issues,
328
+ duration: Date.now() - startTime,
329
+ };
330
+ }
331
+ /**
332
+ * 4. Disabled State Styling
333
+ * Ensure disabled states are consistent
334
+ */
335
+ async checkDisabledStateStyling() {
336
+ const startTime = Date.now();
337
+ const issues = [];
338
+ const files = await this.getTsxFiles();
339
+ for (const file of files) {
340
+ const content = fs.readFileSync(file, "utf8");
341
+ const lines = content.split("\n");
342
+ for (let i = 0; i < lines.length; i++) {
343
+ const line = lines[i];
344
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
345
+ // Check for disabled prop without cursor-not-allowed
346
+ if (/disabled[=:]/.test(line)) {
347
+ const hasNotAllowed = /cursor-not-allowed|pointer-events-none/.test(contextBlock);
348
+ const hasDisabledStyles = /disabled:/.test(contextBlock);
349
+ const isBuiltInComponent = /<Button|<IconButton|<TextField|<Select|<Checkbox|<Radio/.test(line);
350
+ if (!isBuiltInComponent && !hasNotAllowed && !hasDisabledStyles) {
351
+ issues.push({
352
+ file,
353
+ line: i + 1,
354
+ type: "disabled-no-cursor",
355
+ severity: "info",
356
+ message: "Disabled element without cursor-not-allowed",
357
+ suggestion: "Add disabled:cursor-not-allowed disabled:opacity-50",
358
+ snippet: line.trim().substring(0, 80),
359
+ });
360
+ }
361
+ }
362
+ // Check for hardcoded disabled opacity
363
+ if (/disabled.*opacity-(?!50)(\d+)/.test(line)) {
364
+ const opacityMatch = line.match(/opacity-(\d+)/);
365
+ if (opacityMatch && !["50", "60"].includes(opacityMatch[1])) {
366
+ issues.push({
367
+ file,
368
+ line: i + 1,
369
+ type: "inconsistent-disabled-opacity",
370
+ severity: "info",
371
+ message: `Disabled opacity-${opacityMatch[1]} (standard is opacity-50)`,
372
+ suggestion: "Use disabled:opacity-50 for consistency",
373
+ snippet: line.trim().substring(0, 80),
374
+ });
375
+ }
376
+ }
377
+ }
378
+ }
379
+ return {
380
+ name: "Disabled State Styling",
381
+ passed: true, // Info only
382
+ blocking: false,
383
+ issues,
384
+ duration: Date.now() - startTime,
385
+ };
386
+ }
387
+ /**
388
+ * 5. Cursor Consistency
389
+ * Ensure correct cursor for each element type
390
+ */
391
+ async checkCursorConsistency() {
392
+ const startTime = Date.now();
393
+ const issues = [];
394
+ const files = await this.getTsxFiles();
395
+ for (const file of files) {
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
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
401
+ // Check for onClick without cursor-pointer
402
+ if (/onClick=/.test(line)) {
403
+ const isInteractiveElement = /<(Button|IconButton|a|Link|MenuItem|Tab|Chip)/.test(line);
404
+ const hasCursor = /cursor-/.test(contextBlock);
405
+ const isDisabled = /disabled/.test(contextBlock);
406
+ if (!isInteractiveElement && !hasCursor && !isDisabled) {
407
+ issues.push({
408
+ file,
409
+ line: i + 1,
410
+ type: "onclick-no-cursor",
411
+ severity: "info",
412
+ message: "Clickable element without cursor-pointer",
413
+ suggestion: "Add cursor-pointer to indicate clickability",
414
+ snippet: line.trim().substring(0, 80),
415
+ });
416
+ }
417
+ }
418
+ // Check for cursor-pointer on non-interactive elements
419
+ if (/cursor-pointer/.test(line) &&
420
+ !/(onClick|href|to=|role=["']button)/.test(contextBlock)) {
421
+ issues.push({
422
+ file,
423
+ line: i + 1,
424
+ type: "cursor-pointer-not-interactive",
425
+ severity: "info",
426
+ message: "cursor-pointer on non-interactive element",
427
+ suggestion: "Only use cursor-pointer on actually clickable elements",
428
+ snippet: line.trim().substring(0, 80),
429
+ });
430
+ }
431
+ // Check for loading states without cursor-wait
432
+ if (/(isLoading|loading|isPending)\s*&&/.test(line)) {
433
+ const hasCursorWait = /cursor-wait/.test(contextBlock);
434
+ // This is optional, so just info
435
+ }
436
+ }
437
+ }
438
+ return {
439
+ name: "Cursor Consistency",
440
+ passed: true, // Info only
441
+ blocking: false,
442
+ issues,
443
+ duration: Date.now() - startTime,
444
+ };
445
+ }
446
+ /**
447
+ * 6. Scale Feedback Consistency
448
+ * Ensure active:scale values are consistent across components
449
+ */
450
+ async checkScaleFeedbackConsistency() {
451
+ const startTime = Date.now();
452
+ const issues = [];
453
+ const files = await this.getTsxFiles();
454
+ // Track scale values used
455
+ const scaleValues = new Map();
456
+ const STANDARD_SCALES = ["scale-95", "scale-[0.98]", "scale-[0.97]", "scale-100"];
457
+ for (const file of files) {
458
+ const content = fs.readFileSync(file, "utf8");
459
+ const lines = content.split("\n");
460
+ for (let i = 0; i < lines.length; i++) {
461
+ const line = lines[i];
462
+ // Track active:scale-* patterns
463
+ const scaleMatch = line.match(/active:scale-\[?([^\s\]"']+)\]?/);
464
+ if (scaleMatch) {
465
+ const value = scaleMatch[1];
466
+ const existing = scaleValues.get(value) || { count: 0, files: [] };
467
+ existing.count++;
468
+ if (!existing.files.includes(file))
469
+ existing.files.push(file);
470
+ scaleValues.set(value, existing);
471
+ }
472
+ // Check for non-standard scale values
473
+ const nonStandardScale = line.match(/active:scale-\[([0-9.]+)\]/);
474
+ if (nonStandardScale) {
475
+ const value = parseFloat(nonStandardScale[1]);
476
+ if (value < 0.95 || value > 0.99) {
477
+ issues.push({
478
+ file,
479
+ line: i + 1,
480
+ type: "unusual-scale-value",
481
+ severity: "info",
482
+ message: `Unusual scale value: ${value} (standard is 0.97-0.98)`,
483
+ suggestion: "Use active:scale-[0.98] or active:scale-95 for consistency",
484
+ snippet: line.trim().substring(0, 80),
485
+ });
486
+ }
487
+ }
488
+ }
489
+ }
490
+ // Report scale inconsistencies
491
+ if (scaleValues.size > 2) {
492
+ const sorted = [...scaleValues.entries()].sort((a, b) => b[1].count - a[1].count);
493
+ issues.push({
494
+ file: "codebase-wide",
495
+ line: 0,
496
+ type: "scale-inconsistency",
497
+ severity: "info",
498
+ message: `${scaleValues.size} different active:scale values detected`,
499
+ suggestion: `Standardize on 1-2 scale values. Usage: ${sorted.map(([v, d]) => `${v}(${d.count})`).join(", ")}`,
500
+ });
501
+ }
502
+ return {
503
+ name: "Scale Feedback Consistency",
504
+ passed: true, // Info only
505
+ blocking: false,
506
+ issues,
507
+ duration: Date.now() - startTime,
508
+ };
509
+ }
510
+ /**
511
+ * 7. Loading Spinner Consistency
512
+ * Ensure loading spinners use consistent sizing and patterns
513
+ */
514
+ async checkLoadingSpinnerConsistency() {
515
+ const startTime = Date.now();
516
+ const issues = [];
517
+ const files = await this.getTsxFiles();
518
+ // Track spinner sizes
519
+ const spinnerSizes = new Map();
520
+ const STANDARD_SIZES = [16, 20, 24, 32, 40];
521
+ for (const file of files) {
522
+ const content = fs.readFileSync(file, "utf8");
523
+ const lines = content.split("\n");
524
+ for (let i = 0; i < lines.length; i++) {
525
+ const line = lines[i];
526
+ // Check CircularProgress/Spinner size
527
+ const sizeMatch = line.match(/<(?:CircularProgress|Spinner)[^>]*size[=:][\s{]*(\d+)/);
528
+ if (sizeMatch) {
529
+ const size = parseInt(sizeMatch[1]);
530
+ spinnerSizes.set(String(size), (spinnerSizes.get(String(size)) || 0) + 1);
531
+ if (!STANDARD_SIZES.includes(size)) {
532
+ issues.push({
533
+ file,
534
+ line: i + 1,
535
+ type: "non-standard-spinner-size",
536
+ severity: "info",
537
+ message: `Non-standard spinner size: ${size}px`,
538
+ suggestion: `Use standard sizes: ${STANDARD_SIZES.join(", ")}px`,
539
+ snippet: line.trim().substring(0, 80),
540
+ });
541
+ }
542
+ }
543
+ // Check for loading without visual indicator
544
+ if (/isLoading\s*\?\s*[^:]*:[^<]*<Button/.test(line) || /loading\s*\?\s*[^:]*:[^<]*<Button/.test(line)) {
545
+ const hasSpinner = /CircularProgress|Spinner|animate-spin/.test(content.substring(Math.max(0, content.indexOf(line) - 200), content.indexOf(line) + 200));
546
+ if (!hasSpinner) {
547
+ issues.push({
548
+ file,
549
+ line: i + 1,
550
+ type: "loading-no-spinner",
551
+ severity: "info",
552
+ message: "Loading state without visual spinner indicator",
553
+ suggestion: "Add CircularProgress or Spinner component during loading",
554
+ snippet: line.trim().substring(0, 80),
555
+ });
556
+ }
557
+ }
558
+ }
559
+ }
560
+ return {
561
+ name: "Loading Spinner Consistency",
562
+ passed: true, // Info only
563
+ blocking: false,
564
+ issues,
565
+ duration: Date.now() - startTime,
566
+ };
567
+ }
568
+ /**
569
+ * 8. Reduced Motion Support (Accessibility)
570
+ * Check for prefers-reduced-motion support in animations
571
+ */
572
+ async checkReducedMotionSupport() {
573
+ const startTime = Date.now();
574
+ const issues = [];
575
+ const files = await this.getTsxFiles();
576
+ let hasReducedMotionSupport = false;
577
+ let animationCount = 0;
578
+ for (const file of files) {
579
+ const content = fs.readFileSync(file, "utf8");
580
+ // Check for reduced motion support
581
+ if (/prefers-reduced-motion|motion-reduce|motion-safe/.test(content)) {
582
+ hasReducedMotionSupport = true;
583
+ }
584
+ // Count animation usage
585
+ const animations = (content.match(/animate-|transition-|@keyframes/g) || []).length;
586
+ animationCount += animations;
587
+ const lines = content.split("\n");
588
+ for (let i = 0; i < lines.length; i++) {
589
+ const line = lines[i];
590
+ // Check for complex animations without reduced motion alternative
591
+ if (/animate-(?:bounce|pulse|ping|spin)/.test(line)) {
592
+ const contextBlock = content.substring(Math.max(0, content.indexOf(line) - 100), content.indexOf(line) + line.length + 100);
593
+ if (!/motion-reduce|prefers-reduced-motion/.test(contextBlock)) {
594
+ issues.push({
595
+ file,
596
+ line: i + 1,
597
+ type: "animation-no-reduced-motion",
598
+ severity: "info",
599
+ message: "Animation without reduced-motion alternative",
600
+ suggestion: "Add motion-reduce:animate-none for accessibility",
601
+ snippet: line.trim().substring(0, 80),
602
+ });
603
+ }
604
+ }
605
+ }
606
+ }
607
+ // Global check: if we have many animations but no reduced motion support
608
+ if (animationCount > 20 && !hasReducedMotionSupport) {
609
+ issues.push({
610
+ file: "codebase-wide",
611
+ line: 0,
612
+ type: "no-reduced-motion-global",
613
+ severity: "warning",
614
+ message: `${animationCount} animations found but no prefers-reduced-motion support detected`,
615
+ suggestion: "Add global CSS: @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; } }",
616
+ });
617
+ }
618
+ return {
619
+ name: "Reduced Motion Support",
620
+ passed: issues.filter((i) => i.severity === "error").length === 0,
621
+ blocking: false,
622
+ issues,
623
+ duration: Date.now() - startTime,
624
+ };
625
+ }
626
+ /**
627
+ * 9. Async Operation Feedback
628
+ * Check that async operations provide success/error feedback
629
+ */
630
+ async checkAsyncOperationFeedback() {
631
+ const startTime = Date.now();
632
+ const issues = [];
633
+ const files = await this.getTsxFiles();
634
+ for (const file of files) {
635
+ const content = fs.readFileSync(file, "utf8");
636
+ const lines = content.split("\n");
637
+ // Check for fetch/mutation without success feedback
638
+ for (let i = 0; i < lines.length; i++) {
639
+ const line = lines[i];
640
+ // Look for async handlers that might need feedback
641
+ if (/async.*=>\s*\{/.test(line) && /fetch\(|\.mutate|\.mutateAsync/.test(content)) {
642
+ const fnBlock = content.substring(content.indexOf(line), Math.min(content.indexOf(line) + 500, content.length));
643
+ // Check if there's success feedback
644
+ const hasSuccessFeedback = /success\(|toast\.|showToast|setSuccess|onSuccess/.test(fnBlock);
645
+ const hasErrorFeedback = /catch|\.catch|error\(|showError|onError/.test(fnBlock);
646
+ // Only flag if it has error handling but no success feedback (intentional pattern check)
647
+ if (hasErrorFeedback && !hasSuccessFeedback && /response\.ok|\.then/.test(fnBlock)) {
648
+ // Skip if it's clearly a background operation
649
+ if (!/sync|refresh|poll|interval/i.test(fnBlock)) {
650
+ issues.push({
651
+ file,
652
+ line: i + 1,
653
+ type: "async-no-success-feedback",
654
+ severity: "info",
655
+ message: "Async operation has error handling but no visible success feedback",
656
+ suggestion: "Add success toast/notification after successful operations",
657
+ snippet: line.trim().substring(0, 60),
658
+ });
659
+ }
660
+ }
661
+ }
662
+ }
663
+ }
664
+ return {
665
+ name: "Async Operation Feedback",
666
+ passed: true, // Info only
667
+ blocking: false,
668
+ issues,
669
+ duration: Date.now() - startTime,
670
+ };
671
+ }
672
+ /**
673
+ * 10. Transition Property Consistency
674
+ * Ensure transition properties are consistent (all vs specific)
675
+ */
676
+ async checkTransitionConsistency() {
677
+ const startTime = Date.now();
678
+ const issues = [];
679
+ const files = await this.getTsxFiles();
680
+ const transitionTypes = new Map();
681
+ for (const file of files) {
682
+ const content = fs.readFileSync(file, "utf8");
683
+ const lines = content.split("\n");
684
+ for (let i = 0; i < lines.length; i++) {
685
+ const line = lines[i];
686
+ // Track transition-all vs specific transitions
687
+ if (/transition-all/.test(line)) {
688
+ transitionTypes.set("all", (transitionTypes.get("all") || 0) + 1);
689
+ }
690
+ if (/transition-(?:colors|opacity|shadow|transform)/.test(line)) {
691
+ const match = line.match(/transition-(colors|opacity|shadow|transform)/);
692
+ if (match) {
693
+ transitionTypes.set(match[1], (transitionTypes.get(match[1]) || 0) + 1);
694
+ }
695
+ }
696
+ // Check for transition-all on performance-critical elements
697
+ if (/transition-all/.test(line) && /<(?:img|Image|video|canvas)/i.test(line)) {
698
+ issues.push({
699
+ file,
700
+ line: i + 1,
701
+ type: "transition-all-media",
702
+ severity: "warning",
703
+ message: "transition-all on media element may cause performance issues",
704
+ suggestion: "Use specific transitions: transition-opacity or transition-transform",
705
+ snippet: line.trim().substring(0, 80),
706
+ });
707
+ }
708
+ }
709
+ }
710
+ // Report if mixed patterns
711
+ if (transitionTypes.size > 3) {
712
+ const sorted = [...transitionTypes.entries()].sort((a, b) => b[1] - a[1]);
713
+ issues.push({
714
+ file: "codebase-wide",
715
+ line: 0,
716
+ type: "transition-inconsistency",
717
+ severity: "info",
718
+ message: `Multiple transition patterns detected`,
719
+ suggestion: `Consider standardizing. Usage: ${sorted.map(([t, c]) => `${t}(${c})`).join(", ")}`,
720
+ });
721
+ }
722
+ return {
723
+ name: "Transition Consistency",
724
+ passed: issues.filter((i) => i.severity === "error").length === 0,
725
+ blocking: false,
726
+ issues,
727
+ duration: Date.now() - startTime,
728
+ };
729
+ }
730
+ /**
731
+ * 11. Touch/Gesture Feedback
732
+ * Check for mobile touch states and gesture feedback
733
+ */
734
+ async checkTouchGestureFeedback() {
735
+ const startTime = Date.now();
736
+ const issues = [];
737
+ const files = await this.getTsxFiles();
738
+ for (const file of files) {
739
+ const content = fs.readFileSync(file, "utf8");
740
+ const lines = content.split("\n");
741
+ for (let i = 0; i < lines.length; i++) {
742
+ const line = lines[i];
743
+ // Check for touch-action usage consistency
744
+ if (/touch-action/.test(line)) {
745
+ const touchActionMatch = line.match(/touch-action[:\s]+["']?(\w+)/);
746
+ if (touchActionMatch) {
747
+ // touch-action: none can cause accessibility issues
748
+ if (touchActionMatch[1] === "none") {
749
+ issues.push({
750
+ file,
751
+ line: i + 1,
752
+ type: "touch-action-none",
753
+ severity: "warning",
754
+ message: "touch-action: none disables all touch gestures",
755
+ suggestion: "Use touch-action: pan-x, pan-y, or manipulation instead for better UX",
756
+ snippet: line.trim().substring(0, 80),
757
+ });
758
+ }
759
+ }
760
+ }
761
+ // Check for tap-highlight removal without alternative
762
+ if (/-webkit-tap-highlight-color:\s*transparent/.test(line)) {
763
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
764
+ const hasActiveFeedback = /active:|:active/.test(contextBlock);
765
+ if (!hasActiveFeedback) {
766
+ issues.push({
767
+ file,
768
+ line: i + 1,
769
+ type: "tap-highlight-no-alternative",
770
+ severity: "info",
771
+ message: "Tap highlight removed without alternative touch feedback",
772
+ suggestion: "Add active:bg-* or active:scale-* for touch feedback",
773
+ snippet: line.trim().substring(0, 80),
774
+ });
775
+ }
776
+ }
777
+ // Check for swipeable elements without visual affordance
778
+ if (/onTouchStart|onSwipe|swipeable|Swipeable/.test(line)) {
779
+ const contextBlock = lines.slice(Math.max(0, i - 5), Math.min(i + 10, lines.length)).join("\n");
780
+ const hasSwipeIndicator = /swipe|drag|arrow|chevron|indicator/i.test(contextBlock);
781
+ if (!hasSwipeIndicator) {
782
+ issues.push({
783
+ file,
784
+ line: i + 1,
785
+ type: "swipe-no-affordance",
786
+ severity: "info",
787
+ message: "Swipeable element may lack visual affordance",
788
+ suggestion: "Add visual indicators (arrows, dots) to show swipe capability",
789
+ snippet: line.trim().substring(0, 80),
790
+ });
791
+ }
792
+ }
793
+ }
794
+ }
795
+ return {
796
+ name: "Touch/Gesture Feedback",
797
+ passed: issues.filter((i) => i.severity === "error").length === 0,
798
+ blocking: false,
799
+ issues,
800
+ duration: Date.now() - startTime,
801
+ };
802
+ }
803
+ /**
804
+ * 12. Scroll Snap Consistency
805
+ * Check for consistent scroll-snap patterns
806
+ */
807
+ async checkScrollSnapConsistency() {
808
+ const startTime = Date.now();
809
+ const issues = [];
810
+ const files = await this.getTsxFiles();
811
+ const scrollSnapTypes = new Map();
812
+ for (const file of files) {
813
+ const content = fs.readFileSync(file, "utf8");
814
+ const lines = content.split("\n");
815
+ for (let i = 0; i < lines.length; i++) {
816
+ const line = lines[i];
817
+ // Track scroll-snap-type patterns
818
+ const snapTypeMatch = line.match(/scroll-snap-type[:\s]+["']?([^;"'\s}]+)/);
819
+ if (snapTypeMatch) {
820
+ const type = snapTypeMatch[1];
821
+ scrollSnapTypes.set(type, (scrollSnapTypes.get(type) || 0) + 1);
822
+ }
823
+ // Check for snap container without snap children
824
+ if (/snap-(?:x|y|both|mandatory|proximity)/.test(line)) {
825
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
826
+ const hasSnapChildren = /snap-(?:start|center|end|align)/.test(contextBlock);
827
+ if (!hasSnapChildren) {
828
+ issues.push({
829
+ file,
830
+ line: i + 1,
831
+ type: "snap-container-no-children",
832
+ severity: "info",
833
+ message: "Scroll snap container may lack snap-aligned children",
834
+ suggestion: "Add snap-start, snap-center, or snap-end to child elements",
835
+ snippet: line.trim().substring(0, 80),
836
+ });
837
+ }
838
+ }
839
+ // Check for scroll-behavior: smooth
840
+ if (/scroll-behavior:\s*smooth|scroll-smooth/.test(line)) {
841
+ const contextBlock = content.substring(Math.max(0, content.indexOf(line) - 200), content.indexOf(line) + 200);
842
+ // Should respect reduced motion
843
+ if (!/motion-reduce|prefers-reduced-motion/.test(contextBlock)) {
844
+ issues.push({
845
+ file,
846
+ line: i + 1,
847
+ type: "smooth-scroll-no-motion-check",
848
+ severity: "info",
849
+ message: "Smooth scroll without reduced-motion consideration",
850
+ suggestion: "Add motion-reduce:scroll-auto for accessibility",
851
+ snippet: line.trim().substring(0, 80),
852
+ });
853
+ }
854
+ }
855
+ }
856
+ }
857
+ // Report inconsistent snap types
858
+ if (scrollSnapTypes.size > 2) {
859
+ const sorted = [...scrollSnapTypes.entries()].sort((a, b) => b[1] - a[1]);
860
+ issues.push({
861
+ file: "codebase-wide",
862
+ line: 0,
863
+ type: "scroll-snap-inconsistency",
864
+ severity: "info",
865
+ message: `${scrollSnapTypes.size} different scroll-snap-type values detected`,
866
+ suggestion: `Standardize snap behavior. Usage: ${sorted.map(([t, c]) => `${t}(${c})`).join(", ")}`,
867
+ });
868
+ }
869
+ return {
870
+ name: "Scroll Snap Consistency",
871
+ passed: true, // Info only
872
+ blocking: false,
873
+ issues,
874
+ duration: Date.now() - startTime,
875
+ };
876
+ }
877
+ /**
878
+ * 13. Drag and Drop Feedback
879
+ * Check for DnD visual indicators and feedback
880
+ */
881
+ async checkDragDropFeedback() {
882
+ const startTime = Date.now();
883
+ const issues = [];
884
+ const files = await this.getTsxFiles();
885
+ for (const file of files) {
886
+ const content = fs.readFileSync(file, "utf8");
887
+ const lines = content.split("\n");
888
+ for (let i = 0; i < lines.length; i++) {
889
+ const line = lines[i];
890
+ // Check for draggable without visual feedback
891
+ if (/draggable[=:]|onDragStart|useDrag|Draggable/.test(line)) {
892
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
893
+ // Check for drag cursor
894
+ const hasDragCursor = /cursor-(?:grab|move|grabbing)/.test(contextBlock);
895
+ if (!hasDragCursor) {
896
+ issues.push({
897
+ file,
898
+ line: i + 1,
899
+ type: "draggable-no-cursor",
900
+ severity: "info",
901
+ message: "Draggable element without grab cursor",
902
+ suggestion: "Add cursor-grab and active:cursor-grabbing for drag feedback",
903
+ snippet: line.trim().substring(0, 80),
904
+ });
905
+ }
906
+ // Check for drag visual feedback (opacity, scale, shadow)
907
+ const hasDragVisual = /dragging|isDragging|opacity|shadow|scale/.test(contextBlock);
908
+ if (!hasDragVisual) {
909
+ issues.push({
910
+ file,
911
+ line: i + 1,
912
+ type: "draggable-no-visual",
913
+ severity: "info",
914
+ message: "Draggable element may lack visual drag state",
915
+ suggestion: "Add opacity-50 or shadow-lg during drag for visual feedback",
916
+ snippet: line.trim().substring(0, 80),
917
+ });
918
+ }
919
+ }
920
+ // Check for drop zone visual feedback
921
+ if (/onDrop|onDragOver|onDragEnter|useDrop|Droppable/.test(line)) {
922
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
923
+ const hasDropVisual = /isOver|isDragOver|dragOver|drop.*highlight|border.*dashed/.test(contextBlock);
924
+ if (!hasDropVisual) {
925
+ issues.push({
926
+ file,
927
+ line: i + 1,
928
+ type: "dropzone-no-visual",
929
+ severity: "info",
930
+ message: "Drop zone may lack visual feedback on drag over",
931
+ suggestion: "Add border highlight or background change when dragging over",
932
+ snippet: line.trim().substring(0, 80),
933
+ });
934
+ }
935
+ }
936
+ }
937
+ }
938
+ return {
939
+ name: "Drag and Drop Feedback",
940
+ passed: true, // Info only
941
+ blocking: false,
942
+ issues,
943
+ duration: Date.now() - startTime,
944
+ };
945
+ }
946
+ /**
947
+ * 14. Input Validation Feedback
948
+ * Check for real-time validation visual feedback
949
+ */
950
+ async checkInputValidationFeedback() {
951
+ const startTime = Date.now();
952
+ const issues = [];
953
+ const files = await this.getTsxFiles();
954
+ for (const file of files) {
955
+ const content = fs.readFileSync(file, "utf8");
956
+ const lines = content.split("\n");
957
+ for (let i = 0; i < lines.length; i++) {
958
+ const line = lines[i];
959
+ // Check for form validation without visual feedback
960
+ if (/(?:errors|error)\[["']?\w+["']?\]|formState\.errors|fieldState\.error/.test(line)) {
961
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
962
+ // Check for error visual styling
963
+ const hasErrorStyling = /error|destructive|red|border-.*error|text-.*error|ring-.*error/.test(contextBlock);
964
+ if (!hasErrorStyling) {
965
+ issues.push({
966
+ file,
967
+ line: i + 1,
968
+ type: "validation-no-visual",
969
+ severity: "info",
970
+ message: "Form validation error without visual feedback",
971
+ suggestion: "Add red border or text color for validation errors",
972
+ snippet: line.trim().substring(0, 80),
973
+ });
974
+ }
975
+ // Check for error shake animation
976
+ const hasShakeAnimation = /shake|animate-shake|wiggle/.test(contextBlock);
977
+ // This is optional but nice to have - not flagging as issue
978
+ }
979
+ // Check for success validation feedback (e.g., checkmark)
980
+ if (/isValid|isValidating|validation.*success/.test(line)) {
981
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
982
+ const hasSuccessIndicator = /check|success|green|valid.*icon/.test(contextBlock);
983
+ // Optional - good to have but not required
984
+ }
985
+ // Check for TextField/Input with error prop
986
+ if (/<(?:TextField|Input)[^>]*error[=:]/.test(line)) {
987
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
988
+ const hasHelperText = /helperText|FormHelperText|error.*message/.test(contextBlock);
989
+ if (!hasHelperText) {
990
+ issues.push({
991
+ file,
992
+ line: i + 1,
993
+ type: "input-error-no-message",
994
+ severity: "info",
995
+ message: "Input with error state but no helper text",
996
+ suggestion: "Add helperText to explain the validation error",
997
+ snippet: line.trim().substring(0, 80),
998
+ });
999
+ }
1000
+ }
1001
+ }
1002
+ }
1003
+ return {
1004
+ name: "Input Validation Feedback",
1005
+ passed: true, // Info only
1006
+ blocking: false,
1007
+ issues,
1008
+ duration: Date.now() - startTime,
1009
+ };
1010
+ }
1011
+ /**
1012
+ * 15. Toast/Notification Timing
1013
+ * Check for consistent notification durations
1014
+ */
1015
+ async checkToastNotificationTiming() {
1016
+ const startTime = Date.now();
1017
+ const issues = [];
1018
+ const files = await this.getTsxFiles();
1019
+ // Standard durations (in ms)
1020
+ const STANDARD_DURATIONS = [3000, 5000, 7000, 10000];
1021
+ const toastDurations = new Map();
1022
+ for (const file of files) {
1023
+ const content = fs.readFileSync(file, "utf8");
1024
+ const lines = content.split("\n");
1025
+ for (let i = 0; i < lines.length; i++) {
1026
+ const line = lines[i];
1027
+ // Check for toast/notification duration
1028
+ const durationMatch = line.match(/(?:duration|autoHideDuration|timeout)[=:]\s*\{?\s*(\d+)/);
1029
+ if (durationMatch && /toast|notification|snackbar|alert/i.test(content.substring(Math.max(0, content.indexOf(line) - 100), content.indexOf(line) + 100))) {
1030
+ const duration = parseInt(durationMatch[1]);
1031
+ toastDurations.set(duration, (toastDurations.get(duration) || 0) + 1);
1032
+ // Check for very short durations (< 2s)
1033
+ if (duration < 2000) {
1034
+ issues.push({
1035
+ file,
1036
+ line: i + 1,
1037
+ type: "toast-too-short",
1038
+ severity: "warning",
1039
+ message: `Toast duration ${duration}ms is too short to read`,
1040
+ suggestion: "Use at least 3000ms (3s) for readable notifications",
1041
+ snippet: line.trim().substring(0, 80),
1042
+ });
1043
+ }
1044
+ // Check for very long durations (> 15s) without dismiss
1045
+ if (duration > 15000) {
1046
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
1047
+ const hasDismiss = /dismiss|close|onClose/.test(contextBlock);
1048
+ if (!hasDismiss) {
1049
+ issues.push({
1050
+ file,
1051
+ line: i + 1,
1052
+ type: "toast-too-long-no-dismiss",
1053
+ severity: "info",
1054
+ message: `Toast duration ${duration}ms is long - ensure user can dismiss`,
1055
+ suggestion: "Add dismiss button for long-duration notifications",
1056
+ snippet: line.trim().substring(0, 80),
1057
+ });
1058
+ }
1059
+ }
1060
+ // Check for non-standard durations
1061
+ if (!STANDARD_DURATIONS.includes(duration) && duration >= 2000 && duration <= 15000) {
1062
+ issues.push({
1063
+ file,
1064
+ line: i + 1,
1065
+ type: "toast-non-standard-duration",
1066
+ severity: "info",
1067
+ message: `Non-standard toast duration: ${duration}ms`,
1068
+ suggestion: `Use standard durations: ${STANDARD_DURATIONS.join(", ")}ms`,
1069
+ snippet: line.trim().substring(0, 80),
1070
+ });
1071
+ }
1072
+ }
1073
+ // Check for success toast after action
1074
+ if (/success\(|showSuccess|toast\.success/.test(line)) {
1075
+ // Good - has success feedback
1076
+ }
1077
+ // Check for error toast persistence
1078
+ if (/error\(|showError|toast\.error/.test(line)) {
1079
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
1080
+ const hasLongerDuration = /duration.*[5-9]\d{3}|duration.*1\d{4}|persistent|autoHide.*false/.test(contextBlock);
1081
+ if (!hasLongerDuration) {
1082
+ issues.push({
1083
+ file,
1084
+ line: i + 1,
1085
+ type: "error-toast-short",
1086
+ severity: "info",
1087
+ message: "Error notification may auto-dismiss too quickly",
1088
+ suggestion: "Error toasts should stay longer (5-7s) or be manually dismissable",
1089
+ snippet: line.trim().substring(0, 80),
1090
+ });
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+ // Report inconsistent durations
1096
+ if (toastDurations.size > 3) {
1097
+ const sorted = [...toastDurations.entries()].sort((a, b) => b[1] - a[1]);
1098
+ issues.push({
1099
+ file: "codebase-wide",
1100
+ line: 0,
1101
+ type: "toast-duration-inconsistency",
1102
+ severity: "info",
1103
+ message: `${toastDurations.size} different toast durations detected`,
1104
+ suggestion: `Standardize on 2-3 durations. Usage: ${sorted.map(([d, c]) => `${d}ms(${c})`).join(", ")}`,
1105
+ });
1106
+ }
1107
+ return {
1108
+ name: "Toast/Notification Timing",
1109
+ passed: issues.filter((i) => i.severity === "error").length === 0,
1110
+ blocking: false,
1111
+ issues,
1112
+ duration: Date.now() - startTime,
1113
+ };
1114
+ }
1115
+ /**
1116
+ * 16. Skeleton-to-Content Match
1117
+ * Check that skeleton sizes match actual loaded content
1118
+ */
1119
+ async checkSkeletonContentMatch() {
1120
+ const startTime = Date.now();
1121
+ const issues = [];
1122
+ const files = await this.getTsxFiles();
1123
+ const skeletonDimensions = new Map();
1124
+ for (const file of files) {
1125
+ const content = fs.readFileSync(file, "utf8");
1126
+ const lines = content.split("\n");
1127
+ for (let i = 0; i < lines.length; i++) {
1128
+ const line = lines[i];
1129
+ if (/<Skeleton\b/.test(line)) {
1130
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
1131
+ const widthMatch = contextBlock.match(/(?:width|w)=["'{]\s*(\d+|[\w-]+)/);
1132
+ const heightMatch = contextBlock.match(/(?:height|h)=["'{]\s*(\d+|[\w-]+)/);
1133
+ if (widthMatch || heightMatch) {
1134
+ const dim = `${widthMatch?.[1] || "auto"}x${heightMatch?.[1] || "auto"}`;
1135
+ const existing = skeletonDimensions.get(dim) || [];
1136
+ existing.push({ file, line: i + 1 });
1137
+ skeletonDimensions.set(dim, existing);
1138
+ }
1139
+ if (!widthMatch && !heightMatch && !/className=["'][^"']*(?:w-|h-)/.test(contextBlock)) {
1140
+ issues.push({
1141
+ file,
1142
+ line: i + 1,
1143
+ type: "skeleton-no-dimensions",
1144
+ severity: "info",
1145
+ message: "Skeleton without explicit dimensions may cause layout shift",
1146
+ suggestion: "Add width/height props or Tailwind w-/h- classes matching content size",
1147
+ snippet: line.trim().substring(0, 80),
1148
+ });
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1153
+ if (skeletonDimensions.size > 10) {
1154
+ issues.push({
1155
+ file: "codebase-wide",
1156
+ line: 0,
1157
+ type: "many-skeleton-dimensions",
1158
+ severity: "info",
1159
+ message: `${skeletonDimensions.size} different skeleton dimension combinations detected`,
1160
+ suggestion: "Consider standardizing skeleton sizes for consistency",
1161
+ });
1162
+ }
1163
+ return {
1164
+ name: "Skeleton-to-Content Match",
1165
+ passed: true,
1166
+ blocking: false,
1167
+ issues,
1168
+ duration: Date.now() - startTime,
1169
+ };
1170
+ }
1171
+ /**
1172
+ * 17. Empty State Patterns
1173
+ * Check for consistent empty state component usage
1174
+ */
1175
+ async checkEmptyStatePatterns() {
1176
+ const startTime = Date.now();
1177
+ const issues = [];
1178
+ const files = await this.getTsxFiles();
1179
+ const emptyStatePatterns = new Map();
1180
+ for (const file of files) {
1181
+ const content = fs.readFileSync(file, "utf8");
1182
+ const lines = content.split("\n");
1183
+ for (let i = 0; i < lines.length; i++) {
1184
+ const line = lines[i];
1185
+ if (/(?:data|items|results|list)(?:\?)?\.length\s*===?\s*0|isEmpty|noData|noResults/i.test(line)) {
1186
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
1187
+ const usesEmptyStateComponent = /<EmptyState|<NoData|<NoResults|<Empty\b/i.test(contextBlock);
1188
+ const hasInlineEmpty = /No\s+\w+\s+(?:found|available|yet)|Nothing\s+to\s+show|Empty/i.test(contextBlock);
1189
+ const hasIcon = /<(?:Icon|SvgIcon|\w+Icon)|📭|🔍|📋/.test(contextBlock);
1190
+ const hasAction = /<Button|onClick|href=/.test(contextBlock);
1191
+ if (!usesEmptyStateComponent && hasInlineEmpty) {
1192
+ emptyStatePatterns.set("inline-text", (emptyStatePatterns.get("inline-text") || 0) + 1);
1193
+ if (!hasIcon) {
1194
+ issues.push({
1195
+ file,
1196
+ line: i + 1,
1197
+ type: "empty-state-no-icon",
1198
+ severity: "info",
1199
+ message: "Empty state without visual icon",
1200
+ suggestion: "Add an icon to make empty states more visually distinct",
1201
+ snippet: line.trim().substring(0, 80),
1202
+ });
1203
+ }
1204
+ if (!hasAction) {
1205
+ issues.push({
1206
+ file,
1207
+ line: i + 1,
1208
+ type: "empty-state-no-action",
1209
+ severity: "info",
1210
+ message: "Empty state without call-to-action",
1211
+ suggestion: "Consider adding a button or link to help users take action",
1212
+ snippet: line.trim().substring(0, 80),
1213
+ });
1214
+ }
1215
+ }
1216
+ else if (usesEmptyStateComponent) {
1217
+ emptyStatePatterns.set("component", (emptyStatePatterns.get("component") || 0) + 1);
1218
+ }
1219
+ }
1220
+ }
1221
+ }
1222
+ if (emptyStatePatterns.get("inline-text") && emptyStatePatterns.get("component")) {
1223
+ issues.push({
1224
+ file: "codebase-wide",
1225
+ line: 0,
1226
+ type: "mixed-empty-state-patterns",
1227
+ severity: "info",
1228
+ message: `Mixed empty state patterns: ${emptyStatePatterns.get("component")} components, ${emptyStatePatterns.get("inline-text")} inline`,
1229
+ suggestion: "Standardize on EmptyState component for consistency",
1230
+ });
1231
+ }
1232
+ return {
1233
+ name: "Empty State Patterns",
1234
+ passed: true,
1235
+ blocking: false,
1236
+ issues,
1237
+ duration: Date.now() - startTime,
1238
+ };
1239
+ }
1240
+ /**
1241
+ * 18. Selection Feedback
1242
+ * Check for multi-select and checkbox visual states
1243
+ */
1244
+ async checkSelectionFeedback() {
1245
+ const startTime = Date.now();
1246
+ const issues = [];
1247
+ const files = await this.getTsxFiles();
1248
+ for (const file of files) {
1249
+ const content = fs.readFileSync(file, "utf8");
1250
+ const lines = content.split("\n");
1251
+ for (let i = 0; i < lines.length; i++) {
1252
+ const line = lines[i];
1253
+ if (/selectedItems|selectedRows|selection|isSelected|onSelect/i.test(line)) {
1254
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
1255
+ const hasSelectedStyle = /selected.*bg-|bg-.*selected|ring.*selected|border.*selected|data-\[selected\]/i.test(contextBlock);
1256
+ const hasCheckbox = /<Checkbox|<input.*type=["']checkbox/.test(contextBlock);
1257
+ if (!hasSelectedStyle && !hasCheckbox) {
1258
+ issues.push({
1259
+ file,
1260
+ line: i + 1,
1261
+ type: "selection-no-visual",
1262
+ severity: "info",
1263
+ message: "Selection logic without clear visual feedback",
1264
+ suggestion: "Add background color or border change for selected items",
1265
+ snippet: line.trim().substring(0, 80),
1266
+ });
1267
+ }
1268
+ const hasMultiSelect = /selectedItems|selectedRows|Set<|new Set/.test(contextBlock);
1269
+ const hasSelectAll = /selectAll|toggleAll|Select All/i.test(content);
1270
+ if (hasMultiSelect && !hasSelectAll) {
1271
+ issues.push({
1272
+ file,
1273
+ line: i + 1,
1274
+ type: "multi-select-no-select-all",
1275
+ severity: "info",
1276
+ message: "Multi-select without Select All option",
1277
+ suggestion: "Add Select All checkbox for bulk operations",
1278
+ snippet: line.trim().substring(0, 80),
1279
+ });
1280
+ }
1281
+ }
1282
+ if (/<(?:tr|TableRow)[^>]*onClick/.test(line)) {
1283
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
1284
+ if (!/hover:|cursor-pointer/.test(contextBlock)) {
1285
+ issues.push({
1286
+ file,
1287
+ line: i + 1,
1288
+ type: "clickable-row-no-hover",
1289
+ severity: "info",
1290
+ message: "Clickable table row without hover feedback",
1291
+ suggestion: "Add hover:bg-* and cursor-pointer for clickable rows",
1292
+ snippet: line.trim().substring(0, 80),
1293
+ });
1294
+ }
1295
+ }
1296
+ }
1297
+ }
1298
+ return {
1299
+ name: "Selection Feedback",
1300
+ passed: true,
1301
+ blocking: false,
1302
+ issues,
1303
+ duration: Date.now() - startTime,
1304
+ };
1305
+ }
1306
+ /**
1307
+ * 19. Accordion Animation
1308
+ * Check for consistent expand/collapse animations
1309
+ */
1310
+ async checkAccordionAnimation() {
1311
+ const startTime = Date.now();
1312
+ const issues = [];
1313
+ const files = await this.getTsxFiles();
1314
+ const animationPatterns = new Map();
1315
+ for (const file of files) {
1316
+ const content = fs.readFileSync(file, "utf8");
1317
+ const lines = content.split("\n");
1318
+ for (let i = 0; i < lines.length; i++) {
1319
+ const line = lines[i];
1320
+ if (/<(?:Accordion|Collapsible|Disclosure|Expandable)\b/i.test(line) ||
1321
+ /isOpen|isExpanded|expanded|collapsed/i.test(line)) {
1322
+ const contextBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
1323
+ const hasTransition = /transition-|animate-/.test(contextBlock);
1324
+ const hasMaxHeight = /max-h-|maxHeight/.test(contextBlock);
1325
+ const hasFramerMotion = /motion\.|AnimatePresence|variants/.test(contextBlock);
1326
+ const hasRadixAnimation = /data-\[state=/.test(contextBlock);
1327
+ if (hasTransition)
1328
+ animationPatterns.set("tailwind-transition", (animationPatterns.get("tailwind-transition") || 0) + 1);
1329
+ if (hasFramerMotion)
1330
+ animationPatterns.set("framer-motion", (animationPatterns.get("framer-motion") || 0) + 1);
1331
+ if (hasRadixAnimation)
1332
+ animationPatterns.set("radix-data-state", (animationPatterns.get("radix-data-state") || 0) + 1);
1333
+ if (/height.*transition|transition.*height/.test(contextBlock) && !hasMaxHeight) {
1334
+ issues.push({
1335
+ file,
1336
+ line: i + 1,
1337
+ type: "height-animation-no-max",
1338
+ severity: "info",
1339
+ message: "Height animation may need max-height for smooth transitions",
1340
+ suggestion: "Use max-h-0/max-h-[value] with overflow-hidden for smooth accordion",
1341
+ snippet: line.trim().substring(0, 80),
1342
+ });
1343
+ }
1344
+ const hasConditionalRender = /\{.*&&|isOpen\s*\?|\{.*\?.*:/.test(contextBlock);
1345
+ if (hasConditionalRender && !hasTransition && !hasFramerMotion && !hasRadixAnimation) {
1346
+ issues.push({
1347
+ file,
1348
+ line: i + 1,
1349
+ type: "accordion-no-animation",
1350
+ severity: "info",
1351
+ message: "Collapsible content without animation",
1352
+ suggestion: "Add transition animation for smoother UX",
1353
+ snippet: line.trim().substring(0, 80),
1354
+ });
1355
+ }
1356
+ }
1357
+ }
1358
+ }
1359
+ if (animationPatterns.size > 1) {
1360
+ const sorted = [...animationPatterns.entries()].sort((a, b) => b[1] - a[1]);
1361
+ issues.push({
1362
+ file: "codebase-wide",
1363
+ line: 0,
1364
+ type: "mixed-accordion-animations",
1365
+ severity: "info",
1366
+ message: `Multiple accordion animation patterns detected`,
1367
+ suggestion: `Consider standardizing. Usage: ${sorted.map(([p, c]) => `${p}(${c})`).join(", ")}`,
1368
+ });
1369
+ }
1370
+ return {
1371
+ name: "Accordion Animation",
1372
+ passed: true,
1373
+ blocking: false,
1374
+ issues,
1375
+ duration: Date.now() - startTime,
1376
+ };
1377
+ }
1378
+ /**
1379
+ * 20. Modal Entry/Exit Animation
1380
+ * Check for consistent modal animation patterns
1381
+ */
1382
+ async checkModalAnimation() {
1383
+ const startTime = Date.now();
1384
+ const issues = [];
1385
+ const files = await this.getTsxFiles();
1386
+ const modalAnimations = new Map();
1387
+ for (const file of files) {
1388
+ const content = fs.readFileSync(file, "utf8");
1389
+ const lines = content.split("\n");
1390
+ for (let i = 0; i < lines.length; i++) {
1391
+ const line = lines[i];
1392
+ if (/<(?:Dialog|Modal|Sheet|Drawer|AlertDialog)\b/i.test(line)) {
1393
+ const contextBlock = lines.slice(i, Math.min(i + 30, lines.length)).join("\n");
1394
+ const hasTransition = /transition-|animate-/.test(contextBlock);
1395
+ const hasFramerMotion = /motion\.|AnimatePresence/.test(contextBlock);
1396
+ const hasRadixAnimation = /data-\[state=/.test(contextBlock);
1397
+ if (hasFramerMotion)
1398
+ modalAnimations.set("framer-motion", (modalAnimations.get("framer-motion") || 0) + 1);
1399
+ if (hasRadixAnimation)
1400
+ modalAnimations.set("radix", (modalAnimations.get("radix") || 0) + 1);
1401
+ if (hasTransition && !hasFramerMotion && !hasRadixAnimation) {
1402
+ modalAnimations.set("tailwind", (modalAnimations.get("tailwind") || 0) + 1);
1403
+ }
1404
+ const hasBackdrop = /backdrop|overlay|scrim/i.test(contextBlock);
1405
+ const hasBackdropAnimation = /backdrop.*(?:opacity|fade|transition)|(?:opacity|fade|transition).*backdrop/i.test(contextBlock);
1406
+ if (hasBackdrop && !hasBackdropAnimation && !hasRadixAnimation) {
1407
+ issues.push({
1408
+ file,
1409
+ line: i + 1,
1410
+ type: "modal-backdrop-no-animation",
1411
+ severity: "info",
1412
+ message: "Modal backdrop without fade animation",
1413
+ suggestion: "Add opacity transition to backdrop for polished feel",
1414
+ snippet: line.trim().substring(0, 80),
1415
+ });
1416
+ }
1417
+ if (!hasRadixAnimation && !hasFramerMotion) {
1418
+ const hasExitAnimation = /exit|leave|closing|animate-out/i.test(contextBlock);
1419
+ if (hasTransition && !hasExitAnimation) {
1420
+ issues.push({
1421
+ file,
1422
+ line: i + 1,
1423
+ type: "modal-no-exit-animation",
1424
+ severity: "info",
1425
+ message: "Modal may lack exit animation",
1426
+ suggestion: "Add exit animation for smooth close transition",
1427
+ snippet: line.trim().substring(0, 80),
1428
+ });
1429
+ }
1430
+ }
1431
+ }
1432
+ }
1433
+ }
1434
+ if (modalAnimations.size > 1) {
1435
+ const sorted = [...modalAnimations.entries()].sort((a, b) => b[1] - a[1]);
1436
+ issues.push({
1437
+ file: "codebase-wide",
1438
+ line: 0,
1439
+ type: "mixed-modal-animations",
1440
+ severity: "info",
1441
+ message: `Multiple modal animation patterns detected`,
1442
+ suggestion: `Consider standardizing. Usage: ${sorted.map(([p, c]) => `${p}(${c})`).join(", ")}`,
1443
+ });
1444
+ }
1445
+ return {
1446
+ name: "Modal Entry/Exit Animation",
1447
+ passed: true,
1448
+ blocking: false,
1449
+ issues,
1450
+ duration: Date.now() - startTime,
1451
+ };
1452
+ }
1453
+ /**
1454
+ * 21. Copy-to-Clipboard Feedback
1455
+ * Check for success indication after clipboard operations
1456
+ */
1457
+ async checkCopyFeedback() {
1458
+ const startTime = Date.now();
1459
+ const issues = [];
1460
+ const files = await this.getTsxFiles();
1461
+ for (const file of files) {
1462
+ const content = fs.readFileSync(file, "utf8");
1463
+ const lines = content.split("\n");
1464
+ for (let i = 0; i < lines.length; i++) {
1465
+ const line = lines[i];
1466
+ if (/navigator\.clipboard|writeText|copyToClipboard|useCopy|copy\(/i.test(line)) {
1467
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
1468
+ const hasToast = /toast|notification|snackbar/i.test(contextBlock);
1469
+ const hasIconChange = /copied|check|success|CheckIcon|ClipboardCheck/i.test(contextBlock);
1470
+ const hasTooltip = /tooltip|Tooltip|title=["']Copied/i.test(contextBlock);
1471
+ const hasStateChange = /setCopied|isCopied|copied.*true/.test(contextBlock);
1472
+ if (!hasToast && !hasIconChange && !hasTooltip && !hasStateChange) {
1473
+ issues.push({
1474
+ file,
1475
+ line: i + 1,
1476
+ type: "copy-no-feedback",
1477
+ severity: "warning",
1478
+ message: "Clipboard copy without visual feedback",
1479
+ suggestion: "Add toast, icon change, or tooltip to confirm copy action",
1480
+ snippet: line.trim().substring(0, 80),
1481
+ });
1482
+ }
1483
+ if (hasStateChange) {
1484
+ const hasTimeout = /setTimeout|useTimeout|delay/.test(contextBlock);
1485
+ if (!hasTimeout) {
1486
+ issues.push({
1487
+ file,
1488
+ line: i + 1,
1489
+ type: "copy-state-no-reset",
1490
+ severity: "info",
1491
+ message: "Copy state may not reset after showing 'Copied'",
1492
+ suggestion: "Reset copied state after 2-3 seconds with setTimeout",
1493
+ snippet: line.trim().substring(0, 80),
1494
+ });
1495
+ }
1496
+ }
1497
+ }
1498
+ }
1499
+ }
1500
+ return {
1501
+ name: "Copy-to-Clipboard Feedback",
1502
+ passed: issues.filter((i) => i.severity === "error").length === 0,
1503
+ blocking: false,
1504
+ issues,
1505
+ duration: Date.now() - startTime,
1506
+ };
1507
+ }
1508
+ /**
1509
+ * 22. Infinite Scroll Loading
1510
+ * Check for loading indicators at scroll boundaries
1511
+ */
1512
+ async checkInfiniteScrollLoading() {
1513
+ const startTime = Date.now();
1514
+ const issues = [];
1515
+ const files = await this.getTsxFiles();
1516
+ for (const file of files) {
1517
+ const content = fs.readFileSync(file, "utf8");
1518
+ const lines = content.split("\n");
1519
+ for (let i = 0; i < lines.length; i++) {
1520
+ const line = lines[i];
1521
+ if (/useInfiniteQuery|InfiniteScroll|useInfiniteScroll|fetchNextPage|hasNextPage/i.test(line)) {
1522
+ const contextBlock = lines.slice(i, Math.min(i + 30, lines.length)).join("\n");
1523
+ const hasLoadingIndicator = /isFetchingNextPage|loadingMore|isLoadingMore|loading.*spinner|CircularProgress|Spinner/i.test(contextBlock);
1524
+ const hasLoadMoreButton = /Load\s*More|Show\s*More|loadMore/i.test(contextBlock);
1525
+ if (!hasLoadingIndicator && !hasLoadMoreButton) {
1526
+ issues.push({
1527
+ file,
1528
+ line: i + 1,
1529
+ type: "infinite-scroll-no-loading",
1530
+ severity: "warning",
1531
+ message: "Infinite scroll without visible loading indicator",
1532
+ suggestion: "Show spinner or skeleton when fetching next page",
1533
+ snippet: line.trim().substring(0, 80),
1534
+ });
1535
+ }
1536
+ const hasEndIndicator = /hasNextPage.*false|no.*more|end.*of.*list|reached.*end/i.test(contextBlock);
1537
+ if (!hasEndIndicator) {
1538
+ issues.push({
1539
+ file,
1540
+ line: i + 1,
1541
+ type: "infinite-scroll-no-end",
1542
+ severity: "info",
1543
+ message: "Infinite scroll may not indicate when all items are loaded",
1544
+ suggestion: "Show 'No more items' message when hasNextPage is false",
1545
+ snippet: line.trim().substring(0, 80),
1546
+ });
1547
+ }
1548
+ }
1549
+ }
1550
+ }
1551
+ return {
1552
+ name: "Infinite Scroll Loading",
1553
+ passed: issues.filter((i) => i.severity === "error").length === 0,
1554
+ blocking: false,
1555
+ issues,
1556
+ duration: Date.now() - startTime,
1557
+ };
1558
+ }
1559
+ /**
1560
+ * 23. Filter Active State
1561
+ * Check for visual feedback when filters are applied
1562
+ */
1563
+ async checkFilterActiveState() {
1564
+ const startTime = Date.now();
1565
+ const issues = [];
1566
+ const files = await this.getTsxFiles();
1567
+ for (const file of files) {
1568
+ const content = fs.readFileSync(file, "utf8");
1569
+ const lines = content.split("\n");
1570
+ for (let i = 0; i < lines.length; i++) {
1571
+ const line = lines[i];
1572
+ if (/filters?State|activeFilters?|appliedFilters?|filterValues?/i.test(line)) {
1573
+ const contextBlock = lines.slice(i, Math.min(i + 30, lines.length)).join("\n");
1574
+ const hasFilterCount = /filterCount|activeCount|badge|\.length|\.size/i.test(contextBlock);
1575
+ const hasBadgeComponent = /<Badge|<Chip|<Tag|count=/i.test(contextBlock);
1576
+ if (hasFilterCount && !hasBadgeComponent) {
1577
+ issues.push({
1578
+ file,
1579
+ line: i + 1,
1580
+ type: "filter-count-no-badge",
1581
+ severity: "info",
1582
+ message: "Filter count tracked but may not be visually displayed",
1583
+ suggestion: "Show badge with active filter count on filter button",
1584
+ snippet: line.trim().substring(0, 80),
1585
+ });
1586
+ }
1587
+ const hasClearAll = /clearAll|resetFilters?|clearFilters?|remove.*all/i.test(contextBlock);
1588
+ if (!hasClearAll) {
1589
+ issues.push({
1590
+ file,
1591
+ line: i + 1,
1592
+ type: "no-clear-all-filters",
1593
+ severity: "info",
1594
+ message: "Filter management without Clear All option",
1595
+ suggestion: "Add 'Clear All' button when filters are active",
1596
+ snippet: line.trim().substring(0, 80),
1597
+ });
1598
+ }
1599
+ }
1600
+ if (/<(?:Filter|FilterMenu|FilterPopover|FilterDropdown)\b/i.test(line)) {
1601
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
1602
+ const hasActiveStyle = /active|selected|bg-primary|variant=["']default/.test(contextBlock);
1603
+ if (!hasActiveStyle) {
1604
+ issues.push({
1605
+ file,
1606
+ line: i + 1,
1607
+ type: "filter-trigger-no-active-state",
1608
+ severity: "info",
1609
+ message: "Filter trigger may not show when filters are active",
1610
+ suggestion: "Change button style or add badge when filters are applied",
1611
+ snippet: line.trim().substring(0, 80),
1612
+ });
1613
+ }
1614
+ }
1615
+ if (/FilterChip|FilterTag|activeFilter/i.test(line)) {
1616
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
1617
+ const hasRemove = /onRemove|onDelete|onClear|×|✕|close|CloseIcon/i.test(contextBlock);
1618
+ if (!hasRemove) {
1619
+ issues.push({
1620
+ file,
1621
+ line: i + 1,
1622
+ type: "filter-chip-no-remove",
1623
+ severity: "info",
1624
+ message: "Filter chip without remove/clear button",
1625
+ suggestion: "Add X button to allow removing individual filters",
1626
+ snippet: line.trim().substring(0, 80),
1627
+ });
1628
+ }
1629
+ }
1630
+ }
1631
+ }
1632
+ return {
1633
+ name: "Filter Active State",
1634
+ passed: true,
1635
+ blocking: false,
1636
+ issues,
1637
+ duration: Date.now() - startTime,
1638
+ };
1639
+ }
1640
+ /**
1641
+ * 24. Optimistic Updates
1642
+ * Check for optimistic UI patterns in mutations
1643
+ */
1644
+ async checkOptimisticUpdates() {
1645
+ const startTime = Date.now();
1646
+ const issues = [];
1647
+ const files = await this.getTsxFiles();
1648
+ for (const file of files) {
1649
+ const content = fs.readFileSync(file, "utf8");
1650
+ const lines = content.split("\n");
1651
+ for (let i = 0; i < lines.length; i++) {
1652
+ const line = lines[i];
1653
+ // Check for mutations that could benefit from optimistic updates
1654
+ if (/useMutation|mutateAsync|\.mutate\(/.test(line)) {
1655
+ const contextBlock = lines.slice(Math.max(0, i - 5), Math.min(i + 20, lines.length)).join("\n");
1656
+ const hasOptimistic = /optimistic|onMutate.*return|previousData|rollback/i.test(contextBlock);
1657
+ const isLikeOrToggle = /like|favorite|bookmark|toggle|follow|vote/i.test(contextBlock);
1658
+ // Toggle-type actions should use optimistic updates
1659
+ if (isLikeOrToggle && !hasOptimistic) {
1660
+ issues.push({
1661
+ file,
1662
+ line: i + 1,
1663
+ type: "toggle-no-optimistic",
1664
+ severity: "info",
1665
+ message: "Toggle action without optimistic update",
1666
+ suggestion: "Add optimistic update for instant feedback on like/toggle actions",
1667
+ snippet: line.trim().substring(0, 80),
1668
+ });
1669
+ }
1670
+ // Check for error rollback
1671
+ if (hasOptimistic && !/onError.*previous|rollback|setQueryData/i.test(contextBlock)) {
1672
+ issues.push({
1673
+ file,
1674
+ line: i + 1,
1675
+ type: "optimistic-no-rollback",
1676
+ severity: "warning",
1677
+ message: "Optimistic update may lack error rollback",
1678
+ suggestion: "Add onError handler to rollback optimistic changes on failure",
1679
+ snippet: line.trim().substring(0, 80),
1680
+ });
1681
+ }
1682
+ }
1683
+ // Check for direct state updates that should wait for server
1684
+ if (/setItems|setData|setState/.test(line) && /delete|remove/i.test(line)) {
1685
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
1686
+ const hasAwait = /await|\.then|onSuccess/.test(contextBlock);
1687
+ if (!hasAwait) {
1688
+ issues.push({
1689
+ file,
1690
+ line: i + 1,
1691
+ type: "delete-before-confirm",
1692
+ severity: "info",
1693
+ message: "State update before server confirmation",
1694
+ suggestion: "Consider waiting for server response or implementing optimistic update pattern",
1695
+ snippet: line.trim().substring(0, 80),
1696
+ });
1697
+ }
1698
+ }
1699
+ }
1700
+ }
1701
+ return {
1702
+ name: "Optimistic Updates",
1703
+ passed: issues.filter((i) => i.severity === "error").length === 0,
1704
+ blocking: false,
1705
+ issues,
1706
+ duration: Date.now() - startTime,
1707
+ };
1708
+ }
1709
+ /**
1710
+ * 25. Undo/Redo Patterns
1711
+ * Check for undo functionality on destructive actions
1712
+ */
1713
+ async checkUndoRedoPatterns() {
1714
+ const startTime = Date.now();
1715
+ const issues = [];
1716
+ const files = await this.getTsxFiles();
1717
+ let hasUndoSystem = false;
1718
+ for (const file of files) {
1719
+ const content = fs.readFileSync(file, "utf8");
1720
+ const lines = content.split("\n");
1721
+ // Check for undo system presence
1722
+ if (/useUndo|undo.*redo|history.*stack|command.*pattern/i.test(content)) {
1723
+ hasUndoSystem = true;
1724
+ }
1725
+ for (let i = 0; i < lines.length; i++) {
1726
+ const line = lines[i];
1727
+ // Check for delete actions without undo
1728
+ if (/onDelete|handleDelete|deleteItem|removeItem/i.test(line)) {
1729
+ const contextBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
1730
+ const hasUndo = /undo|Undo|restore|Restore|unarchive/i.test(contextBlock);
1731
+ const hasSoftDelete = /soft.*delete|archive|trash|isDeleted/i.test(contextBlock);
1732
+ const hasConfirmation = /confirm|Confirm|Dialog|Modal/i.test(contextBlock);
1733
+ if (!hasUndo && !hasSoftDelete && !hasConfirmation) {
1734
+ issues.push({
1735
+ file,
1736
+ line: i + 1,
1737
+ type: "delete-no-undo",
1738
+ severity: "info",
1739
+ message: "Delete action without undo option",
1740
+ suggestion: "Add undo toast, soft delete, or confirmation dialog for destructive actions",
1741
+ snippet: line.trim().substring(0, 80),
1742
+ });
1743
+ }
1744
+ }
1745
+ // Check for bulk actions without undo
1746
+ if (/bulkDelete|deleteSelected|removeAll|deleteMany/i.test(line)) {
1747
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
1748
+ const hasUndo = /undo|restore/i.test(contextBlock);
1749
+ if (!hasUndo) {
1750
+ issues.push({
1751
+ file,
1752
+ line: i + 1,
1753
+ type: "bulk-delete-no-undo",
1754
+ severity: "warning",
1755
+ message: "Bulk delete without undo option",
1756
+ suggestion: "Bulk deletes should have undo capability or require extra confirmation",
1757
+ snippet: line.trim().substring(0, 80),
1758
+ });
1759
+ }
1760
+ }
1761
+ // Check for toast with undo action
1762
+ if (/toast\(|showToast|notification\(/i.test(line)) {
1763
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
1764
+ const isDeletion = /delete|remove|archive/i.test(contextBlock);
1765
+ const hasUndoAction = /action.*undo|undo.*action|Undo/i.test(contextBlock);
1766
+ if (isDeletion && !hasUndoAction) {
1767
+ issues.push({
1768
+ file,
1769
+ line: i + 1,
1770
+ type: "delete-toast-no-undo",
1771
+ severity: "info",
1772
+ message: "Deletion toast without undo action",
1773
+ suggestion: "Add 'Undo' action button to deletion confirmation toast",
1774
+ snippet: line.trim().substring(0, 80),
1775
+ });
1776
+ }
1777
+ }
1778
+ }
1779
+ }
1780
+ return {
1781
+ name: "Undo/Redo Patterns",
1782
+ passed: issues.filter((i) => i.severity === "error").length === 0,
1783
+ blocking: false,
1784
+ issues,
1785
+ duration: Date.now() - startTime,
1786
+ };
1787
+ }
1788
+ /**
1789
+ * 26. Keyboard Shortcuts
1790
+ * Check for keyboard navigation and shortcut support
1791
+ */
1792
+ async checkKeyboardShortcuts() {
1793
+ const startTime = Date.now();
1794
+ const issues = [];
1795
+ const files = await this.getTsxFiles();
1796
+ let hasKeyboardShortcuts = false;
1797
+ let hasShortcutHelp = false;
1798
+ for (const file of files) {
1799
+ const content = fs.readFileSync(file, "utf8");
1800
+ const lines = content.split("\n");
1801
+ // Check for keyboard shortcut system
1802
+ if (/useHotkeys|useKeyboard|onKeyDown|KeyboardEvent|Mousetrap|hotkeys-js/i.test(content)) {
1803
+ hasKeyboardShortcuts = true;
1804
+ }
1805
+ if (/shortcut.*help|keyboard.*shortcuts|hotkeys.*modal|shortcut.*dialog|\?.*shortcuts/i.test(content)) {
1806
+ hasShortcutHelp = true;
1807
+ }
1808
+ for (let i = 0; i < lines.length; i++) {
1809
+ const line = lines[i];
1810
+ // Check for actions that commonly need shortcuts
1811
+ if (/<(?:SearchInput|SearchField|Search)\b/i.test(line)) {
1812
+ const contextBlock = content;
1813
+ const hasSearchShortcut = /(?:cmd|ctrl|meta)\+k|\/.*search|focus.*search/i.test(contextBlock);
1814
+ if (!hasSearchShortcut) {
1815
+ issues.push({
1816
+ file,
1817
+ line: i + 1,
1818
+ type: "search-no-shortcut",
1819
+ severity: "info",
1820
+ message: "Search input without keyboard shortcut",
1821
+ suggestion: "Add Cmd/Ctrl+K shortcut to focus search (standard pattern)",
1822
+ snippet: line.trim().substring(0, 80),
1823
+ });
1824
+ }
1825
+ }
1826
+ // Check for keyboard event handlers
1827
+ if (/onKeyDown|onKeyUp|onKeyPress/.test(line)) {
1828
+ const contextBlock = lines.slice(i, Math.min(i + 10, lines.length)).join("\n");
1829
+ // Check for Escape key handling in modals/popups
1830
+ if (/<(?:Dialog|Modal|Popover|Menu)\b/i.test(content.substring(Math.max(0, content.indexOf(line) - 200), content.indexOf(line) + 200))) {
1831
+ const hasEscape = /Escape|key.*27|e\.key\s*===?\s*["']Escape/i.test(contextBlock);
1832
+ if (!hasEscape) {
1833
+ issues.push({
1834
+ file,
1835
+ line: i + 1,
1836
+ type: "modal-no-escape",
1837
+ severity: "info",
1838
+ message: "Keyboard handler may not handle Escape key",
1839
+ suggestion: "Add Escape key to close modals/popups",
1840
+ snippet: line.trim().substring(0, 80),
1841
+ });
1842
+ }
1843
+ }
1844
+ // Check for arrow key navigation in lists
1845
+ if (/list|menu|dropdown|options/i.test(content.substring(Math.max(0, content.indexOf(line) - 100), content.indexOf(line) + 100))) {
1846
+ const hasArrows = /ArrowUp|ArrowDown|Arrow(?:Left|Right)|key.*(?:38|40)/i.test(contextBlock);
1847
+ if (!hasArrows) {
1848
+ issues.push({
1849
+ file,
1850
+ line: i + 1,
1851
+ type: "list-no-arrow-nav",
1852
+ severity: "info",
1853
+ message: "List keyboard handler without arrow navigation",
1854
+ suggestion: "Add ArrowUp/ArrowDown for keyboard navigation",
1855
+ snippet: line.trim().substring(0, 80),
1856
+ });
1857
+ }
1858
+ }
1859
+ }
1860
+ // Check for Enter key on clickable elements
1861
+ if (/<div[^>]*onClick|role=["']button/.test(line)) {
1862
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
1863
+ const hasKeyboard = /onKeyDown|onKeyPress|onKeyUp/.test(contextBlock);
1864
+ const hasTabIndex = /tabIndex/.test(contextBlock);
1865
+ if (!hasKeyboard && hasTabIndex) {
1866
+ issues.push({
1867
+ file,
1868
+ line: i + 1,
1869
+ type: "clickable-no-enter",
1870
+ severity: "info",
1871
+ message: "Focusable clickable element without keyboard handler",
1872
+ suggestion: "Add onKeyDown to handle Enter/Space for keyboard activation",
1873
+ snippet: line.trim().substring(0, 80),
1874
+ });
1875
+ }
1876
+ }
1877
+ }
1878
+ }
1879
+ // Global check for shortcut documentation
1880
+ if (hasKeyboardShortcuts && !hasShortcutHelp) {
1881
+ issues.push({
1882
+ file: "codebase-wide",
1883
+ line: 0,
1884
+ type: "shortcuts-no-help",
1885
+ severity: "info",
1886
+ message: "Keyboard shortcuts exist but no help/documentation found",
1887
+ suggestion: "Add keyboard shortcut help modal (often triggered by '?')",
1888
+ });
1889
+ }
1890
+ return {
1891
+ name: "Keyboard Shortcuts",
1892
+ passed: true,
1893
+ blocking: false,
1894
+ issues,
1895
+ duration: Date.now() - startTime,
1896
+ };
1897
+ }
1898
+ /**
1899
+ * 27. Search Debounce
1900
+ * Check for debouncing on search/filter inputs
1901
+ */
1902
+ async checkSearchDebounce() {
1903
+ const startTime = Date.now();
1904
+ const issues = [];
1905
+ const files = await this.getTsxFiles();
1906
+ for (const file of files) {
1907
+ const content = fs.readFileSync(file, "utf8");
1908
+ const lines = content.split("\n");
1909
+ for (let i = 0; i < lines.length; i++) {
1910
+ const line = lines[i];
1911
+ // Check for search inputs without debounce
1912
+ if (/search|filter|query/i.test(line) && /onChange|onInput/.test(line)) {
1913
+ const contextBlock = lines.slice(Math.max(0, i - 10), Math.min(i + 10, lines.length)).join("\n");
1914
+ const hasDebounce = /debounce|useDebounce|useDeferredValue|setTimeout|lodash.*debounce/i.test(contextBlock);
1915
+ const hasThrottle = /throttle|useThrottle/i.test(contextBlock);
1916
+ if (!hasDebounce && !hasThrottle) {
1917
+ // Check if it triggers an API call
1918
+ const triggersApi = /fetch|api|query|refetch|search.*\(/i.test(contextBlock);
1919
+ if (triggersApi) {
1920
+ issues.push({
1921
+ file,
1922
+ line: i + 1,
1923
+ type: "search-no-debounce",
1924
+ severity: "warning",
1925
+ message: "Search input triggers API without debouncing",
1926
+ suggestion: "Add debounce (300-500ms) to prevent excessive API calls",
1927
+ snippet: line.trim().substring(0, 80),
1928
+ });
1929
+ }
1930
+ }
1931
+ }
1932
+ // Check for useEffect with search dependency
1933
+ if (/useEffect/.test(line)) {
1934
+ const effectBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
1935
+ const hasSearchDep = /\[.*(?:search|query|filter).*\]/i.test(effectBlock);
1936
+ const hasApi = /fetch|api|\.get|\.post/i.test(effectBlock);
1937
+ if (hasSearchDep && hasApi) {
1938
+ const hasDebounce = /debounce|setTimeout|useDeferredValue/i.test(effectBlock);
1939
+ if (!hasDebounce) {
1940
+ issues.push({
1941
+ file,
1942
+ line: i + 1,
1943
+ type: "effect-search-no-debounce",
1944
+ severity: "warning",
1945
+ message: "useEffect with search dependency may fire too frequently",
1946
+ suggestion: "Debounce the search value or use useDeferredValue",
1947
+ snippet: line.trim().substring(0, 80),
1948
+ });
1949
+ }
1950
+ }
1951
+ }
1952
+ // Check debounce timing
1953
+ const debounceMatch = line.match(/debounce[^,]*,\s*(\d+)/i);
1954
+ if (debounceMatch) {
1955
+ const delay = parseInt(debounceMatch[1]);
1956
+ if (delay < 150) {
1957
+ issues.push({
1958
+ file,
1959
+ line: i + 1,
1960
+ type: "debounce-too-short",
1961
+ severity: "info",
1962
+ message: `Debounce delay ${delay}ms may be too short`,
1963
+ suggestion: "Use 300-500ms for search debouncing",
1964
+ snippet: line.trim().substring(0, 80),
1965
+ });
1966
+ }
1967
+ if (delay > 1000) {
1968
+ issues.push({
1969
+ file,
1970
+ line: i + 1,
1971
+ type: "debounce-too-long",
1972
+ severity: "info",
1973
+ message: `Debounce delay ${delay}ms may feel sluggish`,
1974
+ suggestion: "300-500ms is usually optimal for search",
1975
+ snippet: line.trim().substring(0, 80),
1976
+ });
1977
+ }
1978
+ }
1979
+ }
1980
+ }
1981
+ return {
1982
+ name: "Search Debounce",
1983
+ passed: issues.filter((i) => i.severity === "error").length === 0,
1984
+ blocking: false,
1985
+ issues,
1986
+ duration: Date.now() - startTime,
1987
+ };
1988
+ }
1989
+ /**
1990
+ * 28. Confirmation Dialogs
1991
+ * Check for confirmation on destructive actions
1992
+ */
1993
+ async checkConfirmationDialogs() {
1994
+ const startTime = Date.now();
1995
+ const issues = [];
1996
+ const files = await this.getTsxFiles();
1997
+ for (const file of files) {
1998
+ const content = fs.readFileSync(file, "utf8");
1999
+ const lines = content.split("\n");
2000
+ for (let i = 0; i < lines.length; i++) {
2001
+ const line = lines[i];
2002
+ // Check for destructive actions without confirmation
2003
+ if (/handleDelete|onDelete|deleteItem|removeItem|handleRemove/i.test(line)) {
2004
+ const contextBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
2005
+ const hasConfirm = /confirm|Confirm|Dialog|Modal|AlertDialog|window\.confirm/i.test(contextBlock);
2006
+ const hasUndo = /undo|restore/i.test(contextBlock);
2007
+ if (!hasConfirm && !hasUndo) {
2008
+ issues.push({
2009
+ file,
2010
+ line: i + 1,
2011
+ type: "delete-no-confirmation",
2012
+ severity: "warning",
2013
+ message: "Delete action without confirmation dialog",
2014
+ suggestion: "Add confirmation dialog for destructive actions",
2015
+ snippet: line.trim().substring(0, 80),
2016
+ });
2017
+ }
2018
+ }
2019
+ // Check for dangerous operations
2020
+ if (/reset.*data|clear.*all|wipe|purge|truncate/i.test(line)) {
2021
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
2022
+ const hasDoubleConfirm = /type.*confirm|enter.*name|type.*DELETE/i.test(contextBlock);
2023
+ if (!hasDoubleConfirm) {
2024
+ issues.push({
2025
+ file,
2026
+ line: i + 1,
2027
+ type: "dangerous-no-double-confirm",
2028
+ severity: "info",
2029
+ message: "High-risk operation without extra confirmation",
2030
+ suggestion: "Require typing 'DELETE' or item name for dangerous operations",
2031
+ snippet: line.trim().substring(0, 80),
2032
+ });
2033
+ }
2034
+ }
2035
+ // Check confirmation dialog patterns
2036
+ if (/<(?:AlertDialog|ConfirmDialog|ConfirmationModal)\b/i.test(line)) {
2037
+ const contextBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
2038
+ // Check for clear destructive action button
2039
+ const hasDestructiveButton = /variant=["']destructive|color=["']error|danger|red/i.test(contextBlock);
2040
+ if (!hasDestructiveButton) {
2041
+ issues.push({
2042
+ file,
2043
+ line: i + 1,
2044
+ type: "confirm-no-destructive-style",
2045
+ severity: "info",
2046
+ message: "Confirmation dialog without destructive button styling",
2047
+ suggestion: "Use red/destructive variant for delete confirmation button",
2048
+ snippet: line.trim().substring(0, 80),
2049
+ });
2050
+ }
2051
+ // Check for clear cancel option
2052
+ const hasCancelButton = /cancel|Cancel|onCancel|close|Close|dismiss/i.test(contextBlock);
2053
+ if (!hasCancelButton) {
2054
+ issues.push({
2055
+ file,
2056
+ line: i + 1,
2057
+ type: "confirm-no-cancel",
2058
+ severity: "warning",
2059
+ message: "Confirmation dialog may lack clear cancel option",
2060
+ suggestion: "Always provide obvious cancel/close option",
2061
+ snippet: line.trim().substring(0, 80),
2062
+ });
2063
+ }
2064
+ }
2065
+ }
2066
+ }
2067
+ return {
2068
+ name: "Confirmation Dialogs",
2069
+ passed: issues.filter((i) => i.severity === "error").length === 0,
2070
+ blocking: false,
2071
+ issues,
2072
+ duration: Date.now() - startTime,
2073
+ };
2074
+ }
2075
+ /**
2076
+ * 29. Progress Indicators
2077
+ * Check for progress tracking on multi-step operations
2078
+ */
2079
+ async checkProgressIndicators() {
2080
+ const startTime = Date.now();
2081
+ const issues = [];
2082
+ const files = await this.getTsxFiles();
2083
+ const progressPatterns = new Map();
2084
+ for (const file of files) {
2085
+ const content = fs.readFileSync(file, "utf8");
2086
+ const lines = content.split("\n");
2087
+ for (let i = 0; i < lines.length; i++) {
2088
+ const line = lines[i];
2089
+ // Check for multi-step processes
2090
+ if (/step|Step|currentStep|activeStep/i.test(line)) {
2091
+ const contextBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
2092
+ const hasStepper = /<Stepper|<Steps|<ProgressSteps|StepIndicator/i.test(contextBlock);
2093
+ const hasProgress = /progress|Progress|step.*of.*total/i.test(contextBlock);
2094
+ if (!hasStepper && !hasProgress) {
2095
+ issues.push({
2096
+ file,
2097
+ line: i + 1,
2098
+ type: "steps-no-indicator",
2099
+ severity: "info",
2100
+ message: "Multi-step process without progress indicator",
2101
+ suggestion: "Add Stepper component to show progress through steps",
2102
+ snippet: line.trim().substring(0, 80),
2103
+ });
2104
+ }
2105
+ // Track stepper pattern
2106
+ if (hasStepper) {
2107
+ progressPatterns.set("stepper", (progressPatterns.get("stepper") || 0) + 1);
2108
+ }
2109
+ }
2110
+ // Check for file uploads
2111
+ if (/upload|Upload|handleUpload|onUpload/i.test(line)) {
2112
+ const contextBlock = lines.slice(i, Math.min(i + 25, lines.length)).join("\n");
2113
+ const hasProgress = /progress|Progress|percent|loaded.*total/i.test(contextBlock);
2114
+ const hasProgressBar = /<Progress|<LinearProgress|ProgressBar/i.test(contextBlock);
2115
+ if (!hasProgress && !hasProgressBar) {
2116
+ issues.push({
2117
+ file,
2118
+ line: i + 1,
2119
+ type: "upload-no-progress",
2120
+ severity: "warning",
2121
+ message: "File upload without progress indicator",
2122
+ suggestion: "Show upload progress bar with percentage",
2123
+ snippet: line.trim().substring(0, 80),
2124
+ });
2125
+ }
2126
+ }
2127
+ // Check for bulk operations
2128
+ if (/bulk|batch|process.*items|import.*data/i.test(line)) {
2129
+ const contextBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
2130
+ const hasProgress = /progress|processed|current.*total|\d+.*of.*\d+/i.test(contextBlock);
2131
+ if (!hasProgress) {
2132
+ issues.push({
2133
+ file,
2134
+ line: i + 1,
2135
+ type: "bulk-no-progress",
2136
+ severity: "info",
2137
+ message: "Bulk operation without progress tracking",
2138
+ suggestion: "Show 'X of Y processed' indicator for bulk operations",
2139
+ snippet: line.trim().substring(0, 80),
2140
+ });
2141
+ }
2142
+ }
2143
+ // Check progress bar accessibility
2144
+ if (/<Progress|<LinearProgress|<CircularProgress/i.test(line)) {
2145
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
2146
+ const hasAriaLabel = /aria-label|aria-valuetext|aria-valuenow/i.test(contextBlock);
2147
+ if (!hasAriaLabel) {
2148
+ issues.push({
2149
+ file,
2150
+ line: i + 1,
2151
+ type: "progress-no-aria",
2152
+ severity: "info",
2153
+ message: "Progress indicator may lack screen reader support",
2154
+ suggestion: "Add aria-label or aria-valuetext for accessibility",
2155
+ snippet: line.trim().substring(0, 80),
2156
+ });
2157
+ }
2158
+ }
2159
+ }
2160
+ }
2161
+ return {
2162
+ name: "Progress Indicators",
2163
+ passed: issues.filter((i) => i.severity === "error").length === 0,
2164
+ blocking: false,
2165
+ issues,
2166
+ duration: Date.now() - startTime,
2167
+ };
2168
+ }
2169
+ /**
2170
+ * 30. Error Recovery
2171
+ * Check for retry mechanisms and error recovery UI
2172
+ */
2173
+ async checkErrorRecovery() {
2174
+ const startTime = Date.now();
2175
+ const issues = [];
2176
+ const files = await this.getTsxFiles();
2177
+ for (const file of files) {
2178
+ const content = fs.readFileSync(file, "utf8");
2179
+ const lines = content.split("\n");
2180
+ for (let i = 0; i < lines.length; i++) {
2181
+ const line = lines[i];
2182
+ // Check for error states without retry
2183
+ if (/isError|error\s*&&|\.error\s*\?|hasError/i.test(line)) {
2184
+ const contextBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
2185
+ const hasRetry = /retry|Retry|refetch|try.*again|reload/i.test(contextBlock);
2186
+ const hasErrorMessage = /error.*message|message.*error|Error:|display.*error/i.test(contextBlock);
2187
+ if (!hasRetry) {
2188
+ issues.push({
2189
+ file,
2190
+ line: i + 1,
2191
+ type: "error-no-retry",
2192
+ severity: "info",
2193
+ message: "Error state without retry option",
2194
+ suggestion: "Add 'Try Again' or 'Retry' button for recoverable errors",
2195
+ snippet: line.trim().substring(0, 80),
2196
+ });
2197
+ }
2198
+ if (!hasErrorMessage) {
2199
+ issues.push({
2200
+ file,
2201
+ line: i + 1,
2202
+ type: "error-no-message",
2203
+ severity: "warning",
2204
+ message: "Error state without clear error message",
2205
+ suggestion: "Display user-friendly error message explaining what went wrong",
2206
+ snippet: line.trim().substring(0, 80),
2207
+ });
2208
+ }
2209
+ }
2210
+ // Check for network error handling
2211
+ if (/catch|\.catch|onError/i.test(line)) {
2212
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
2213
+ const hasOfflineCheck = /offline|navigator\.onLine|network.*error/i.test(contextBlock);
2214
+ const hasTimeoutCheck = /timeout|TIMEOUT|timed.*out/i.test(contextBlock);
2215
+ // Check if it distinguishes network errors
2216
+ if (/fetch|api|axios/i.test(contextBlock) && !hasOfflineCheck) {
2217
+ issues.push({
2218
+ file,
2219
+ line: i + 1,
2220
+ type: "no-offline-handling",
2221
+ severity: "info",
2222
+ message: "API error handling may not detect offline state",
2223
+ suggestion: "Check navigator.onLine and show appropriate offline message",
2224
+ snippet: line.trim().substring(0, 80),
2225
+ });
2226
+ }
2227
+ }
2228
+ // Check for ErrorBoundary usage
2229
+ if (/<ErrorBoundary/i.test(line)) {
2230
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
2231
+ const hasFallback = /fallback|Fallback|renderError|FallbackComponent/i.test(contextBlock);
2232
+ const hasRetry = /retry|reset|recover/i.test(contextBlock);
2233
+ if (!hasFallback) {
2234
+ issues.push({
2235
+ file,
2236
+ line: i + 1,
2237
+ type: "boundary-no-fallback",
2238
+ severity: "warning",
2239
+ message: "ErrorBoundary without fallback UI",
2240
+ suggestion: "Add fallback prop with user-friendly error UI",
2241
+ snippet: line.trim().substring(0, 80),
2242
+ });
2243
+ }
2244
+ if (!hasRetry) {
2245
+ issues.push({
2246
+ file,
2247
+ line: i + 1,
2248
+ type: "boundary-no-retry",
2249
+ severity: "info",
2250
+ message: "ErrorBoundary without retry mechanism",
2251
+ suggestion: "Add reset/retry functionality to recover from errors",
2252
+ snippet: line.trim().substring(0, 80),
2253
+ });
2254
+ }
2255
+ }
2256
+ }
2257
+ }
2258
+ return {
2259
+ name: "Error Recovery",
2260
+ passed: issues.filter((i) => i.severity === "error").length === 0,
2261
+ blocking: false,
2262
+ issues,
2263
+ duration: Date.now() - startTime,
2264
+ };
2265
+ }
2266
+ /**
2267
+ * 31. Success Celebrations
2268
+ * Check for micro-animations and feedback on successful actions
2269
+ */
2270
+ async checkSuccessCelebrations() {
2271
+ const startTime = Date.now();
2272
+ const issues = [];
2273
+ const files = await this.getTsxFiles();
2274
+ let hasConfetti = false;
2275
+ let hasSuccessAnimation = false;
2276
+ for (const file of files) {
2277
+ const content = fs.readFileSync(file, "utf8");
2278
+ const lines = content.split("\n");
2279
+ // Check for celebration libraries
2280
+ if (/confetti|canvas-confetti|lottie|celebrate/i.test(content)) {
2281
+ hasConfetti = true;
2282
+ }
2283
+ if (/success.*animate|animate.*success|pulse|bounce.*success/i.test(content)) {
2284
+ hasSuccessAnimation = true;
2285
+ }
2286
+ for (let i = 0; i < lines.length; i++) {
2287
+ const line = lines[i];
2288
+ // Check for significant success events
2289
+ if (/onSuccess|success\(|showSuccess|isSuccess/i.test(line)) {
2290
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
2291
+ // Check for completion/milestone events
2292
+ const isMilestone = /complete|finish|checkout|payment|submit.*order|upgrade/i.test(contextBlock);
2293
+ const hasSpecialFeedback = /confetti|celebrate|animation|animate|party|🎉|✨/i.test(contextBlock);
2294
+ if (isMilestone && !hasSpecialFeedback) {
2295
+ issues.push({
2296
+ file,
2297
+ line: i + 1,
2298
+ type: "milestone-no-celebration",
2299
+ severity: "info",
2300
+ message: "Milestone event without celebratory feedback",
2301
+ suggestion: "Consider adding confetti or special animation for completed purchases/signups",
2302
+ snippet: line.trim().substring(0, 80),
2303
+ });
2304
+ }
2305
+ }
2306
+ // Check for checkmark animations
2307
+ if (/<Check|<SuccessIcon|CheckCircle|CheckIcon/i.test(line)) {
2308
+ const contextBlock = lines.slice(i, Math.min(i + 5, lines.length)).join("\n");
2309
+ const hasAnimation = /animate|motion|transition/i.test(contextBlock);
2310
+ // This is nice-to-have, just track
2311
+ }
2312
+ // Check for form submission success
2313
+ if (/onSubmit.*success|handleSubmit.*success|form.*success/i.test(line)) {
2314
+ const contextBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
2315
+ const hasSuccessFeedback = /toast|notification|success.*message|✓|check/i.test(contextBlock);
2316
+ const hasRedirect = /navigate|redirect|router\.push|window\.location/i.test(contextBlock);
2317
+ if (!hasSuccessFeedback && !hasRedirect) {
2318
+ issues.push({
2319
+ file,
2320
+ line: i + 1,
2321
+ type: "form-success-no-feedback",
2322
+ severity: "info",
2323
+ message: "Form submission success without clear feedback",
2324
+ suggestion: "Show success toast or checkmark animation after submission",
2325
+ snippet: line.trim().substring(0, 80),
2326
+ });
2327
+ }
2328
+ }
2329
+ // Check for save operations
2330
+ if (/save|Save|handleSave|onSave/i.test(line)) {
2331
+ const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
2332
+ const hasSuccessFeedback = /success|saved|toast|notification|check/i.test(contextBlock);
2333
+ const hasAutoSave = /auto.*save|autosave|debounce.*save/i.test(contextBlock);
2334
+ if (!hasSuccessFeedback && !hasAutoSave) {
2335
+ issues.push({
2336
+ file,
2337
+ line: i + 1,
2338
+ type: "save-no-feedback",
2339
+ severity: "info",
2340
+ message: "Save action without success confirmation",
2341
+ suggestion: "Show 'Saved!' indicator or checkmark briefly after save",
2342
+ snippet: line.trim().substring(0, 80),
2343
+ });
2344
+ }
2345
+ }
2346
+ }
2347
+ }
2348
+ return {
2349
+ name: "Success Celebrations",
2350
+ passed: true,
2351
+ blocking: false,
2352
+ issues,
2353
+ duration: Date.now() - startTime,
2354
+ };
2355
+ }
2356
+ /**
2357
+ * Run all checks
2358
+ */
2359
+ async runAll(filter) {
2360
+ const startTime = Date.now();
2361
+ const checks = [];
2362
+ const checkMap = {
2363
+ hover: () => this.checkHoverStateConsistency(),
2364
+ active: () => this.checkActiveStateCoverage(),
2365
+ focus: () => this.checkFocusRingConsistency(),
2366
+ disabled: () => this.checkDisabledStateStyling(),
2367
+ cursor: () => this.checkCursorConsistency(),
2368
+ scale: () => this.checkScaleFeedbackConsistency(),
2369
+ spinner: () => this.checkLoadingSpinnerConsistency(),
2370
+ motion: () => this.checkReducedMotionSupport(),
2371
+ feedback: () => this.checkAsyncOperationFeedback(),
2372
+ transition: () => this.checkTransitionConsistency(),
2373
+ touch: () => this.checkTouchGestureFeedback(),
2374
+ scroll: () => this.checkScrollSnapConsistency(),
2375
+ dnd: () => this.checkDragDropFeedback(),
2376
+ validation: () => this.checkInputValidationFeedback(),
2377
+ toast: () => this.checkToastNotificationTiming(),
2378
+ skeleton: () => this.checkSkeletonContentMatch(),
2379
+ empty: () => this.checkEmptyStatePatterns(),
2380
+ selection: () => this.checkSelectionFeedback(),
2381
+ accordion: () => this.checkAccordionAnimation(),
2382
+ modal: () => this.checkModalAnimation(),
2383
+ copy: () => this.checkCopyFeedback(),
2384
+ infinite: () => this.checkInfiniteScrollLoading(),
2385
+ filter: () => this.checkFilterActiveState(),
2386
+ optimistic: () => this.checkOptimisticUpdates(),
2387
+ undo: () => this.checkUndoRedoPatterns(),
2388
+ keyboard: () => this.checkKeyboardShortcuts(),
2389
+ debounce: () => this.checkSearchDebounce(),
2390
+ confirm: () => this.checkConfirmationDialogs(),
2391
+ progress: () => this.checkProgressIndicators(),
2392
+ recovery: () => this.checkErrorRecovery(),
2393
+ celebrate: () => this.checkSuccessCelebrations(),
2394
+ };
2395
+ if (filter && checkMap[filter]) {
2396
+ checks.push(await checkMap[filter]());
2397
+ }
2398
+ else {
2399
+ for (const check of Object.values(checkMap)) {
2400
+ checks.push(await check());
2401
+ }
2402
+ }
2403
+ const errors = checks.reduce((sum, c) => sum + c.issues.filter((i) => i.severity === "error").length, 0);
2404
+ const warnings = checks.reduce((sum, c) => sum + c.issues.filter((i) => i.severity === "warning").length, 0);
2405
+ return {
2406
+ module: "UI Interactive States",
2407
+ passed: errors === 0,
2408
+ totalDuration: Date.now() - startTime,
2409
+ checks,
2410
+ summary: {
2411
+ total: checks.length,
2412
+ passed: checks.filter((c) => c.passed).length,
2413
+ failed: checks.filter((c) => !c.passed).length,
2414
+ errors,
2415
+ warnings,
2416
+ },
2417
+ };
2418
+ }
2419
+ }
2420
+ exports.UIInteractiveStatesModule = UIInteractiveStatesModule;
2421
+ // CLI ENTRY POINT
2422
+ async function main() {
2423
+ const args = process.argv.slice(2);
2424
+ const filter = args.find((a) => !a.startsWith("-"));
2425
+ const verbose = args.includes("--verbose") || args.includes("-v");
2426
+ const parallel = args.includes("--parallel") || args.includes("-p");
2427
+ const reporter = (0, universal_progress_reporter_1.createUniversalProgressReporter)(path.basename(__filename, ".ts"));
2428
+ const module = new UIInteractiveStatesModule({ verbose, parallel });
2429
+ const result = await module.runAll(filter);
2430
+ console.log((0, console_chars_1.createDivider)(80, "heavy"));
2431
+ console.log(`${console_chars_1.emoji.target} UI INTERACTIVE STATES CHECK`);
2432
+ console.log((0, console_chars_1.createDivider)(80, "heavy"));
2433
+ console.log();
2434
+ for (const check of result.checks) {
2435
+ const icon = check.passed
2436
+ ? console_chars_1.emoji.success
2437
+ : check.issues.some((i) => i.severity === "error")
2438
+ ? console_chars_1.emoji.error
2439
+ : console_chars_1.emoji.warning;
2440
+ console.log(`${icon} ${check.name} (${check.duration}ms)`);
2441
+ if (check.issues.length > 0 && (verbose || check.issues.some((i) => i.severity !== "info"))) {
2442
+ for (const issue of check.issues.slice(0, verbose ? 100 : 5)) {
2443
+ const sevIcon = issue.severity === "error"
2444
+ ? console_chars_1.emoji.error
2445
+ : issue.severity === "warning"
2446
+ ? console_chars_1.emoji.warning
2447
+ : console_chars_1.emoji.info;
2448
+ const relPath = path.relative(process.cwd(), issue.file).replace(/\\/g, "/");
2449
+ console.log(` ${sevIcon} ${relPath}:${issue.line}`);
2450
+ console.log(` ${issue.message}`);
2451
+ console.log(` ${console_chars_1.emoji.hint} ${issue.suggestion}`);
2452
+ }
2453
+ if (!verbose && check.issues.length > 5) {
2454
+ console.log(` ... and ${check.issues.length - 5} more issues`);
2455
+ }
2456
+ }
2457
+ console.log();
2458
+ }
2459
+ console.log((0, console_chars_1.createDivider)(80, "light"));
2460
+ 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`);
2461
+ console.log(`Duration: ${(result.totalDuration / 1000).toFixed(2)}s`);
2462
+ console.log((0, console_chars_1.createDivider)(80, "heavy"));
2463
+ if (result.summary.errors > 0) {
2464
+ console.log(`\n${console_chars_1.emoji.error} INTERACTIVE STATES CHECK FAILED\n`);
2465
+ process.exit(1);
2466
+ }
2467
+ else {
2468
+ console.log(`\n${console_chars_1.emoji.success} INTERACTIVE STATES CHECK PASSED\n`);
2469
+ process.exit(0);
2470
+ }
2471
+ }
2472
+ if (require.main === module)
2473
+ main();
2474
+ //# sourceMappingURL=ui-interactive-states.js.map