@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.
- package/dist/checks/consolidated/auth-storage-state.d.ts +3 -0
- package/dist/checks/consolidated/auth-storage-state.d.ts.map +1 -0
- package/dist/checks/consolidated/auth-storage-state.js +146 -0
- package/dist/checks/consolidated/auth-storage-state.js.map +1 -0
- package/dist/checks/consolidated/business.d.ts +50 -0
- package/dist/checks/consolidated/business.d.ts.map +1 -0
- package/dist/checks/consolidated/business.js +252 -0
- package/dist/checks/consolidated/business.js.map +1 -0
- package/dist/checks/consolidated/caching-strategy.d.ts +104 -0
- package/dist/checks/consolidated/caching-strategy.d.ts.map +1 -0
- package/dist/checks/consolidated/caching-strategy.js +725 -0
- package/dist/checks/consolidated/caching-strategy.js.map +1 -0
- package/dist/checks/consolidated/code-quality.d.ts +83 -0
- package/dist/checks/consolidated/code-quality.d.ts.map +1 -0
- package/dist/checks/consolidated/code-quality.js +445 -0
- package/dist/checks/consolidated/code-quality.js.map +1 -0
- package/dist/checks/consolidated/console-statements.d.ts +32 -0
- package/dist/checks/consolidated/console-statements.d.ts.map +1 -0
- package/dist/checks/consolidated/console-statements.js +304 -0
- package/dist/checks/consolidated/console-statements.js.map +1 -0
- package/dist/checks/consolidated/css-advanced-validation.d.ts +24 -0
- package/dist/checks/consolidated/css-advanced-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/css-advanced-validation.js +415 -0
- package/dist/checks/consolidated/css-advanced-validation.js.map +1 -0
- package/dist/checks/consolidated/css-organization.d.ts +14 -0
- package/dist/checks/consolidated/css-organization.d.ts.map +1 -0
- package/dist/checks/consolidated/css-organization.js +432 -0
- package/dist/checks/consolidated/css-organization.js.map +1 -0
- package/dist/checks/consolidated/css-runtime-validation.d.ts +22 -0
- package/dist/checks/consolidated/css-runtime-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/css-runtime-validation.js +330 -0
- package/dist/checks/consolidated/css-runtime-validation.js.map +1 -0
- package/dist/checks/consolidated/css-variable-validation.d.ts +17 -0
- package/dist/checks/consolidated/css-variable-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/css-variable-validation.js +412 -0
- package/dist/checks/consolidated/css-variable-validation.js.map +1 -0
- package/dist/checks/consolidated/dark-mode-consistency.d.ts +23 -0
- package/dist/checks/consolidated/dark-mode-consistency.d.ts.map +1 -0
- package/dist/checks/consolidated/dark-mode-consistency.js +291 -0
- package/dist/checks/consolidated/dark-mode-consistency.js.map +1 -0
- package/dist/checks/consolidated/database.d.ts +95 -0
- package/dist/checks/consolidated/database.d.ts.map +1 -0
- package/dist/checks/consolidated/database.js +427 -0
- package/dist/checks/consolidated/database.js.map +1 -0
- package/dist/checks/consolidated/e2e-checks.d.ts +52 -0
- package/dist/checks/consolidated/e2e-checks.d.ts.map +1 -0
- package/dist/checks/consolidated/e2e-checks.js +157 -0
- package/dist/checks/consolidated/e2e-checks.js.map +1 -0
- package/dist/checks/consolidated/e2e-regression-coverage.d.ts +14 -0
- package/dist/checks/consolidated/e2e-regression-coverage.d.ts.map +1 -0
- package/dist/checks/consolidated/e2e-regression-coverage.js +151 -0
- package/dist/checks/consolidated/e2e-regression-coverage.js.map +1 -0
- package/dist/checks/consolidated/e2e-validation.d.ts +137 -0
- package/dist/checks/consolidated/e2e-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/e2e-validation.js +1001 -0
- package/dist/checks/consolidated/e2e-validation.js.map +1 -0
- package/dist/checks/consolidated/enterprise-baseline.d.ts +9 -0
- package/dist/checks/consolidated/enterprise-baseline.d.ts.map +1 -0
- package/dist/checks/consolidated/enterprise-baseline.js +277 -0
- package/dist/checks/consolidated/enterprise-baseline.js.map +1 -0
- package/dist/checks/consolidated/generate-pageload-config.d.ts +6 -0
- package/dist/checks/consolidated/generate-pageload-config.d.ts.map +1 -0
- package/dist/checks/consolidated/generate-pageload-config.js +161 -0
- package/dist/checks/consolidated/generate-pageload-config.js.map +1 -0
- package/dist/checks/consolidated/hardened-checks.d.ts +276 -0
- package/dist/checks/consolidated/hardened-checks.d.ts.map +1 -0
- package/dist/checks/consolidated/hardened-checks.js +3056 -0
- package/dist/checks/consolidated/hardened-checks.js.map +1 -0
- package/dist/checks/consolidated/homepage-ux.d.ts +12 -0
- package/dist/checks/consolidated/homepage-ux.d.ts.map +1 -0
- package/dist/checks/consolidated/homepage-ux.js +242 -0
- package/dist/checks/consolidated/homepage-ux.js.map +1 -0
- package/dist/checks/consolidated/images.d.ts +76 -0
- package/dist/checks/consolidated/images.d.ts.map +1 -0
- package/dist/checks/consolidated/images.js +311 -0
- package/dist/checks/consolidated/images.js.map +1 -0
- package/dist/checks/consolidated/import-cycles.d.ts +63 -0
- package/dist/checks/consolidated/import-cycles.d.ts.map +1 -0
- package/dist/checks/consolidated/import-cycles.js +291 -0
- package/dist/checks/consolidated/import-cycles.js.map +1 -0
- package/dist/checks/consolidated/imports.d.ts +112 -0
- package/dist/checks/consolidated/imports.d.ts.map +1 -0
- package/dist/checks/consolidated/imports.js +977 -0
- package/dist/checks/consolidated/imports.js.map +1 -0
- package/dist/checks/consolidated/inline-style-conflicts.d.ts +21 -0
- package/dist/checks/consolidated/inline-style-conflicts.d.ts.map +1 -0
- package/dist/checks/consolidated/inline-style-conflicts.js +300 -0
- package/dist/checks/consolidated/inline-style-conflicts.js.map +1 -0
- package/dist/checks/consolidated/lib-organization.d.ts +12 -0
- package/dist/checks/consolidated/lib-organization.d.ts.map +1 -0
- package/dist/checks/consolidated/lib-organization.js +419 -0
- package/dist/checks/consolidated/lib-organization.js.map +1 -0
- package/dist/checks/consolidated/n-plus-one.d.ts +63 -0
- package/dist/checks/consolidated/n-plus-one.d.ts.map +1 -0
- package/dist/checks/consolidated/n-plus-one.js +331 -0
- package/dist/checks/consolidated/n-plus-one.js.map +1 -0
- package/dist/checks/consolidated/nextjs.d.ts +51 -0
- package/dist/checks/consolidated/nextjs.d.ts.map +1 -0
- package/dist/checks/consolidated/nextjs.js +205 -0
- package/dist/checks/consolidated/nextjs.js.map +1 -0
- package/dist/checks/consolidated/organization.d.ts +54 -0
- package/dist/checks/consolidated/organization.d.ts.map +1 -0
- package/dist/checks/consolidated/organization.js +158 -0
- package/dist/checks/consolidated/organization.js.map +1 -0
- package/dist/checks/consolidated/pageload.d.ts +12 -0
- package/dist/checks/consolidated/pageload.d.ts.map +1 -0
- package/dist/checks/consolidated/pageload.js +138 -0
- package/dist/checks/consolidated/pageload.js.map +1 -0
- package/dist/checks/consolidated/performance.d.ts +112 -0
- package/dist/checks/consolidated/performance.d.ts.map +1 -0
- package/dist/checks/consolidated/performance.js +1546 -0
- package/dist/checks/consolidated/performance.js.map +1 -0
- package/dist/checks/consolidated/quality.d.ts +52 -0
- package/dist/checks/consolidated/quality.d.ts.map +1 -0
- package/dist/checks/consolidated/quality.js +253 -0
- package/dist/checks/consolidated/quality.js.map +1 -0
- package/dist/checks/consolidated/react.d.ts +48 -0
- package/dist/checks/consolidated/react.d.ts.map +1 -0
- package/dist/checks/consolidated/react.js +203 -0
- package/dist/checks/consolidated/react.js.map +1 -0
- package/dist/checks/consolidated/regression-hygiene.d.ts +17 -0
- package/dist/checks/consolidated/regression-hygiene.d.ts.map +1 -0
- package/dist/checks/consolidated/regression-hygiene.js +242 -0
- package/dist/checks/consolidated/regression-hygiene.js.map +1 -0
- package/dist/checks/consolidated/regression.d.ts +20 -0
- package/dist/checks/consolidated/regression.d.ts.map +1 -0
- package/dist/checks/consolidated/regression.js +121 -0
- package/dist/checks/consolidated/regression.js.map +1 -0
- package/dist/checks/consolidated/runtime.d.ts +53 -0
- package/dist/checks/consolidated/runtime.d.ts.map +1 -0
- package/dist/checks/consolidated/runtime.js +160 -0
- package/dist/checks/consolidated/runtime.js.map +1 -0
- package/dist/checks/consolidated/script-performance.d.ts +17 -0
- package/dist/checks/consolidated/script-performance.d.ts.map +1 -0
- package/dist/checks/consolidated/script-performance.js +137 -0
- package/dist/checks/consolidated/script-performance.js.map +1 -0
- package/dist/checks/consolidated/security.d.ts +78 -0
- package/dist/checks/consolidated/security.d.ts.map +1 -0
- package/dist/checks/consolidated/security.js +404 -0
- package/dist/checks/consolidated/security.js.map +1 -0
- package/dist/checks/consolidated/seo.d.ts +31 -0
- package/dist/checks/consolidated/seo.d.ts.map +1 -0
- package/dist/checks/consolidated/seo.js +1438 -0
- package/dist/checks/consolidated/seo.js.map +1 -0
- package/dist/checks/consolidated/sx-prop-deprecation.d.ts +22 -0
- package/dist/checks/consolidated/sx-prop-deprecation.d.ts.map +1 -0
- package/dist/checks/consolidated/sx-prop-deprecation.js +280 -0
- package/dist/checks/consolidated/sx-prop-deprecation.js.map +1 -0
- package/dist/checks/consolidated/tailwind-class-validation.d.ts +25 -0
- package/dist/checks/consolidated/tailwind-class-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/tailwind-class-validation.js +533 -0
- package/dist/checks/consolidated/tailwind-class-validation.js.map +1 -0
- package/dist/checks/consolidated/testing.d.ts +54 -0
- package/dist/checks/consolidated/testing.d.ts.map +1 -0
- package/dist/checks/consolidated/testing.js +163 -0
- package/dist/checks/consolidated/testing.js.map +1 -0
- package/dist/checks/consolidated/typescript.d.ts +3 -0
- package/dist/checks/consolidated/typescript.d.ts.map +1 -0
- package/dist/checks/consolidated/typescript.js +31 -0
- package/dist/checks/consolidated/typescript.js.map +1 -0
- package/dist/checks/consolidated/ui-accessibility-advanced.d.ts +104 -0
- package/dist/checks/consolidated/ui-accessibility-advanced.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-accessibility-advanced.js +689 -0
- package/dist/checks/consolidated/ui-accessibility-advanced.js.map +1 -0
- package/dist/checks/consolidated/ui-accessibility.d.ts +121 -0
- package/dist/checks/consolidated/ui-accessibility.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-accessibility.js +776 -0
- package/dist/checks/consolidated/ui-accessibility.js.map +1 -0
- package/dist/checks/consolidated/ui-advanced-spacing.d.ts +142 -0
- package/dist/checks/consolidated/ui-advanced-spacing.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-advanced-spacing.js +1220 -0
- package/dist/checks/consolidated/ui-advanced-spacing.js.map +1 -0
- package/dist/checks/consolidated/ui-animation-duration.d.ts +108 -0
- package/dist/checks/consolidated/ui-animation-duration.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-animation-duration.js +531 -0
- package/dist/checks/consolidated/ui-animation-duration.js.map +1 -0
- package/dist/checks/consolidated/ui-border-radius.d.ts +90 -0
- package/dist/checks/consolidated/ui-border-radius.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-border-radius.js +519 -0
- package/dist/checks/consolidated/ui-border-radius.js.map +1 -0
- package/dist/checks/consolidated/ui-buttons.d.ts +32 -0
- package/dist/checks/consolidated/ui-buttons.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-buttons.js +481 -0
- package/dist/checks/consolidated/ui-buttons.js.map +1 -0
- package/dist/checks/consolidated/ui-cards.d.ts +29 -0
- package/dist/checks/consolidated/ui-cards.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-cards.js +504 -0
- package/dist/checks/consolidated/ui-cards.js.map +1 -0
- package/dist/checks/consolidated/ui-checks.d.ts +48 -0
- package/dist/checks/consolidated/ui-checks.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-checks.js +264 -0
- package/dist/checks/consolidated/ui-checks.js.map +1 -0
- package/dist/checks/consolidated/ui-cleanup.d.ts +81 -0
- package/dist/checks/consolidated/ui-cleanup.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-cleanup.js +650 -0
- package/dist/checks/consolidated/ui-cleanup.js.map +1 -0
- package/dist/checks/consolidated/ui-components.d.ts +255 -0
- package/dist/checks/consolidated/ui-components.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-components.js +2008 -0
- package/dist/checks/consolidated/ui-components.js.map +1 -0
- package/dist/checks/consolidated/ui-consistency-advanced.d.ts +130 -0
- package/dist/checks/consolidated/ui-consistency-advanced.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-consistency-advanced.js +982 -0
- package/dist/checks/consolidated/ui-consistency-advanced.js.map +1 -0
- package/dist/checks/consolidated/ui-consistency-comprehensive.d.ts +30 -0
- package/dist/checks/consolidated/ui-consistency-comprehensive.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-consistency-comprehensive.js +1018 -0
- package/dist/checks/consolidated/ui-consistency-comprehensive.js.map +1 -0
- package/dist/checks/consolidated/ui-consistency-extended.d.ts +26 -0
- package/dist/checks/consolidated/ui-consistency-extended.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-consistency-extended.js +606 -0
- package/dist/checks/consolidated/ui-consistency-extended.js.map +1 -0
- package/dist/checks/consolidated/ui-data-display.d.ts +103 -0
- package/dist/checks/consolidated/ui-data-display.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-data-display.js +740 -0
- package/dist/checks/consolidated/ui-data-display.js.map +1 -0
- package/dist/checks/consolidated/ui-deprecated.d.ts +22 -0
- package/dist/checks/consolidated/ui-deprecated.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-deprecated.js +336 -0
- package/dist/checks/consolidated/ui-deprecated.js.map +1 -0
- package/dist/checks/consolidated/ui-empty-null-states.d.ts +90 -0
- package/dist/checks/consolidated/ui-empty-null-states.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-empty-null-states.js +511 -0
- package/dist/checks/consolidated/ui-empty-null-states.js.map +1 -0
- package/dist/checks/consolidated/ui-error-states.d.ts +99 -0
- package/dist/checks/consolidated/ui-error-states.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-error-states.js +694 -0
- package/dist/checks/consolidated/ui-error-states.js.map +1 -0
- package/dist/checks/consolidated/ui-feedback-confirmations.d.ts +90 -0
- package/dist/checks/consolidated/ui-feedback-confirmations.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-feedback-confirmations.js +596 -0
- package/dist/checks/consolidated/ui-feedback-confirmations.js.map +1 -0
- package/dist/checks/consolidated/ui-forms.d.ts +32 -0
- package/dist/checks/consolidated/ui-forms.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-forms.js +568 -0
- package/dist/checks/consolidated/ui-forms.js.map +1 -0
- package/dist/checks/consolidated/ui-gradient-shadow.d.ts +90 -0
- package/dist/checks/consolidated/ui-gradient-shadow.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-gradient-shadow.js +568 -0
- package/dist/checks/consolidated/ui-gradient-shadow.js.map +1 -0
- package/dist/checks/consolidated/ui-grid-responsive.d.ts +27 -0
- package/dist/checks/consolidated/ui-grid-responsive.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-grid-responsive.js +441 -0
- package/dist/checks/consolidated/ui-grid-responsive.js.map +1 -0
- package/dist/checks/consolidated/ui-icon-size-tokens.d.ts +104 -0
- package/dist/checks/consolidated/ui-icon-size-tokens.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-icon-size-tokens.js +514 -0
- package/dist/checks/consolidated/ui-icon-size-tokens.js.map +1 -0
- package/dist/checks/consolidated/ui-iconography.d.ts +90 -0
- package/dist/checks/consolidated/ui-iconography.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-iconography.js +565 -0
- package/dist/checks/consolidated/ui-iconography.js.map +1 -0
- package/dist/checks/consolidated/ui-interactive-states.d.ts +240 -0
- package/dist/checks/consolidated/ui-interactive-states.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-interactive-states.js +2474 -0
- package/dist/checks/consolidated/ui-interactive-states.js.map +1 -0
- package/dist/checks/consolidated/ui-layout.d.ts +256 -0
- package/dist/checks/consolidated/ui-layout.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-layout.js +1371 -0
- package/dist/checks/consolidated/ui-layout.js.map +1 -0
- package/dist/checks/consolidated/ui-loading-skeletons.d.ts +11 -0
- package/dist/checks/consolidated/ui-loading-skeletons.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-loading-skeletons.js +145 -0
- package/dist/checks/consolidated/ui-loading-skeletons.js.map +1 -0
- package/dist/checks/consolidated/ui-loading-state-skeletons.d.ts +9 -0
- package/dist/checks/consolidated/ui-loading-state-skeletons.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-loading-state-skeletons.js +125 -0
- package/dist/checks/consolidated/ui-loading-state-skeletons.js.map +1 -0
- package/dist/checks/consolidated/ui-media.d.ts +74 -0
- package/dist/checks/consolidated/ui-media.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-media.js +408 -0
- package/dist/checks/consolidated/ui-media.js.map +1 -0
- package/dist/checks/consolidated/ui-micro-interactions.d.ts +107 -0
- package/dist/checks/consolidated/ui-micro-interactions.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-micro-interactions.js +825 -0
- package/dist/checks/consolidated/ui-micro-interactions.js.map +1 -0
- package/dist/checks/consolidated/ui-microcopy-consistency.d.ts +114 -0
- package/dist/checks/consolidated/ui-microcopy-consistency.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-microcopy-consistency.js +566 -0
- package/dist/checks/consolidated/ui-microcopy-consistency.js.map +1 -0
- package/dist/checks/consolidated/ui-mobile-ux.d.ts +251 -0
- package/dist/checks/consolidated/ui-mobile-ux.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-mobile-ux.js +2212 -0
- package/dist/checks/consolidated/ui-mobile-ux.js.map +1 -0
- package/dist/checks/consolidated/ui-motion-accessibility.d.ts +93 -0
- package/dist/checks/consolidated/ui-motion-accessibility.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-motion-accessibility.js +450 -0
- package/dist/checks/consolidated/ui-motion-accessibility.js.map +1 -0
- package/dist/checks/consolidated/ui-navigation.d.ts +85 -0
- package/dist/checks/consolidated/ui-navigation.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-navigation.js +673 -0
- package/dist/checks/consolidated/ui-navigation.js.map +1 -0
- package/dist/checks/consolidated/ui-patterns.d.ts +174 -0
- package/dist/checks/consolidated/ui-patterns.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-patterns.js +1532 -0
- package/dist/checks/consolidated/ui-patterns.js.map +1 -0
- package/dist/checks/consolidated/ui-responsive.d.ts +89 -0
- package/dist/checks/consolidated/ui-responsive.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-responsive.js +588 -0
- package/dist/checks/consolidated/ui-responsive.js.map +1 -0
- package/dist/checks/consolidated/ui-spacing-standards.d.ts +43 -0
- package/dist/checks/consolidated/ui-spacing-standards.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-spacing-standards.js +874 -0
- package/dist/checks/consolidated/ui-spacing-standards.js.map +1 -0
- package/dist/checks/consolidated/ui-spacing.d.ts +751 -0
- package/dist/checks/consolidated/ui-spacing.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-spacing.js +4996 -0
- package/dist/checks/consolidated/ui-spacing.js.map +1 -0
- package/dist/checks/consolidated/ui-standards-auto-fixer.d.ts +70 -0
- package/dist/checks/consolidated/ui-standards-auto-fixer.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-standards-auto-fixer.js +429 -0
- package/dist/checks/consolidated/ui-standards-auto-fixer.js.map +1 -0
- package/dist/checks/consolidated/ui-standards-enforcement.d.ts +100 -0
- package/dist/checks/consolidated/ui-standards-enforcement.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-standards-enforcement.js +935 -0
- package/dist/checks/consolidated/ui-standards-enforcement.js.map +1 -0
- package/dist/checks/consolidated/ui-state-consistency.d.ts +90 -0
- package/dist/checks/consolidated/ui-state-consistency.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-state-consistency.js +659 -0
- package/dist/checks/consolidated/ui-state-consistency.js.map +1 -0
- package/dist/checks/consolidated/ui-style-validation.d.ts +74 -0
- package/dist/checks/consolidated/ui-style-validation.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-style-validation.js +403 -0
- package/dist/checks/consolidated/ui-style-validation.js.map +1 -0
- package/dist/checks/consolidated/ui-tokens.d.ts +110 -0
- package/dist/checks/consolidated/ui-tokens.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-tokens.js +990 -0
- package/dist/checks/consolidated/ui-tokens.js.map +1 -0
- package/dist/checks/consolidated/ui-typography.d.ts +77 -0
- package/dist/checks/consolidated/ui-typography.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-typography.js +416 -0
- package/dist/checks/consolidated/ui-typography.js.map +1 -0
- package/dist/checks/consolidated/ui-visual-hierarchy.d.ts +90 -0
- package/dist/checks/consolidated/ui-visual-hierarchy.d.ts.map +1 -0
- package/dist/checks/consolidated/ui-visual-hierarchy.js +562 -0
- package/dist/checks/consolidated/ui-visual-hierarchy.js.map +1 -0
- package/dist/checks/consolidated/woocommerce.d.ts +50 -0
- package/dist/checks/consolidated/woocommerce.d.ts.map +1 -0
- package/dist/checks/consolidated/woocommerce.js +198 -0
- package/dist/checks/consolidated/woocommerce.js.map +1 -0
- package/dist/checks/core/api-route-protection.d.ts +2 -0
- package/dist/checks/core/api-route-protection.d.ts.map +1 -0
- package/dist/checks/core/api-route-protection.js +101 -0
- package/dist/checks/core/api-route-protection.js.map +1 -0
- package/dist/checks/core/critical.d.ts +8 -0
- package/dist/checks/core/critical.d.ts.map +1 -0
- package/dist/checks/core/critical.js +200 -0
- package/dist/checks/core/critical.js.map +1 -0
- package/dist/checks/core/database.d.ts +8 -0
- package/dist/checks/core/database.d.ts.map +1 -0
- package/dist/checks/core/database.js +699 -0
- package/dist/checks/core/database.js.map +1 -0
- package/dist/checks/core/development.d.ts +8 -0
- package/dist/checks/core/development.d.ts.map +1 -0
- package/dist/checks/core/development.js +417 -0
- package/dist/checks/core/development.js.map +1 -0
- package/dist/checks/core/hydration-mismatch-check.d.ts +38 -0
- package/dist/checks/core/hydration-mismatch-check.d.ts.map +1 -0
- package/dist/checks/core/hydration-mismatch-check.js +411 -0
- package/dist/checks/core/hydration-mismatch-check.js.map +1 -0
- package/dist/checks/core/performance.d.ts +8 -0
- package/dist/checks/core/performance.d.ts.map +1 -0
- package/dist/checks/core/performance.js +474 -0
- package/dist/checks/core/performance.js.map +1 -0
- package/dist/checks/core/security.d.ts +8 -0
- package/dist/checks/core/security.d.ts.map +1 -0
- package/dist/checks/core/security.js +275 -0
- package/dist/checks/core/security.js.map +1 -0
- package/dist/checks/core/standardized-error-handling.d.ts +43 -0
- package/dist/checks/core/standardized-error-handling.d.ts.map +1 -0
- package/dist/checks/core/standardized-error-handling.js +384 -0
- package/dist/checks/core/standardized-error-handling.js.map +1 -0
- package/dist/checks/core/supercatch.d.ts +8 -0
- package/dist/checks/core/supercatch.d.ts.map +1 -0
- package/dist/checks/core/supercatch.js +750 -0
- package/dist/checks/core/supercatch.js.map +1 -0
- package/dist/checks/core/suppression-check.d.ts +2 -0
- package/dist/checks/core/suppression-check.d.ts.map +1 -0
- package/dist/checks/core/suppression-check.js +129 -0
- package/dist/checks/core/suppression-check.js.map +1 -0
- package/dist/checks/core/ui-quality.d.ts +8 -0
- package/dist/checks/core/ui-quality.d.ts.map +1 -0
- package/dist/checks/core/ui-quality.js +1736 -0
- package/dist/checks/core/ui-quality.js.map +1 -0
- package/dist/checks/core/unused-assets-check.d.ts +2 -0
- package/dist/checks/core/unused-assets-check.d.ts.map +1 -0
- package/dist/checks/core/unused-assets-check.js +112 -0
- package/dist/checks/core/unused-assets-check.js.map +1 -0
- package/dist/checks/core/use-status-ssr-safety.d.ts +34 -0
- package/dist/checks/core/use-status-ssr-safety.d.ts.map +1 -0
- package/dist/checks/core/use-status-ssr-safety.js +283 -0
- package/dist/checks/core/use-status-ssr-safety.js.map +1 -0
- package/dist/checks/email/email-flow-validation.d.ts +23 -0
- package/dist/checks/email/email-flow-validation.d.ts.map +1 -0
- package/dist/checks/email/email-flow-validation.js +468 -0
- package/dist/checks/email/email-flow-validation.js.map +1 -0
- package/dist/checks/email/email-template-db-verification.d.ts +20 -0
- package/dist/checks/email/email-template-db-verification.d.ts.map +1 -0
- package/dist/checks/email/email-template-db-verification.js +46 -0
- package/dist/checks/email/email-template-db-verification.js.map +1 -0
- package/dist/checks/email/email-template-validation.d.ts +24 -0
- package/dist/checks/email/email-template-validation.d.ts.map +1 -0
- package/dist/checks/email/email-template-validation.js +688 -0
- package/dist/checks/email/email-template-validation.js.map +1 -0
- package/dist/checks/jsx/comment-placement.d.ts +45 -0
- package/dist/checks/jsx/comment-placement.d.ts.map +1 -0
- package/dist/checks/jsx/comment-placement.js +316 -0
- package/dist/checks/jsx/comment-placement.js.map +1 -0
- package/dist/checks/specialized/admin-layout-check.d.ts +19 -0
- package/dist/checks/specialized/admin-layout-check.d.ts.map +1 -0
- package/dist/checks/specialized/admin-layout-check.js +166 -0
- package/dist/checks/specialized/admin-layout-check.js.map +1 -0
- package/dist/checks/specialized/client-server-separation.d.ts +14 -0
- package/dist/checks/specialized/client-server-separation.d.ts.map +1 -0
- package/dist/checks/specialized/client-server-separation.js +197 -0
- package/dist/checks/specialized/client-server-separation.js.map +1 -0
- package/dist/checks/specialized/cost-optimization.d.ts +18 -0
- package/dist/checks/specialized/cost-optimization.d.ts.map +1 -0
- package/dist/checks/specialized/cost-optimization.js +78 -0
- package/dist/checks/specialized/cost-optimization.js.map +1 -0
- package/dist/checks/specialized/database-migration-sync.d.ts +21 -0
- package/dist/checks/specialized/database-migration-sync.d.ts.map +1 -0
- package/dist/checks/specialized/database-migration-sync.js +150 -0
- package/dist/checks/specialized/database-migration-sync.js.map +1 -0
- package/dist/checks/specialized/database-model-validation.d.ts +15 -0
- package/dist/checks/specialized/database-model-validation.d.ts.map +1 -0
- package/dist/checks/specialized/database-model-validation.js +35 -0
- package/dist/checks/specialized/database-model-validation.js.map +1 -0
- package/dist/checks/specialized/database-schema-migrations-diff.d.ts +27 -0
- package/dist/checks/specialized/database-schema-migrations-diff.d.ts.map +1 -0
- package/dist/checks/specialized/database-schema-migrations-diff.js +177 -0
- package/dist/checks/specialized/database-schema-migrations-diff.js.map +1 -0
- package/dist/checks/specialized/database-schema-sync.d.ts +23 -0
- package/dist/checks/specialized/database-schema-sync.d.ts.map +1 -0
- package/dist/checks/specialized/database-schema-sync.js +77 -0
- package/dist/checks/specialized/database-schema-sync.js.map +1 -0
- package/dist/checks/specialized/decimal-serialization.d.ts +24 -0
- package/dist/checks/specialized/decimal-serialization.d.ts.map +1 -0
- package/dist/checks/specialized/decimal-serialization.js +400 -0
- package/dist/checks/specialized/decimal-serialization.js.map +1 -0
- package/dist/checks/specialized/detect-router-issues.d.ts +14 -0
- package/dist/checks/specialized/detect-router-issues.d.ts.map +1 -0
- package/dist/checks/specialized/detect-router-issues.js +96 -0
- package/dist/checks/specialized/detect-router-issues.js.map +1 -0
- package/dist/checks/specialized/enum-validation.d.ts +15 -0
- package/dist/checks/specialized/enum-validation.d.ts.map +1 -0
- package/dist/checks/specialized/enum-validation.js +35 -0
- package/dist/checks/specialized/enum-validation.js.map +1 -0
- package/dist/checks/specialized/hash-collision.d.ts +18 -0
- package/dist/checks/specialized/hash-collision.d.ts.map +1 -0
- package/dist/checks/specialized/hash-collision.js +78 -0
- package/dist/checks/specialized/hash-collision.js.map +1 -0
- package/dist/checks/specialized/id-generation-enforcement.d.ts +16 -0
- package/dist/checks/specialized/id-generation-enforcement.d.ts.map +1 -0
- package/dist/checks/specialized/id-generation-enforcement.js +307 -0
- package/dist/checks/specialized/id-generation-enforcement.js.map +1 -0
- package/dist/checks/specialized/image-data-integrity.d.ts +15 -0
- package/dist/checks/specialized/image-data-integrity.d.ts.map +1 -0
- package/dist/checks/specialized/image-data-integrity.js +79 -0
- package/dist/checks/specialized/image-data-integrity.js.map +1 -0
- package/dist/checks/specialized/image-health.d.ts +14 -0
- package/dist/checks/specialized/image-health.d.ts.map +1 -0
- package/dist/checks/specialized/image-health.js +122 -0
- package/dist/checks/specialized/image-health.js.map +1 -0
- package/dist/checks/specialized/image-metadata-validation.d.ts +14 -0
- package/dist/checks/specialized/image-metadata-validation.d.ts.map +1 -0
- package/dist/checks/specialized/image-metadata-validation.js +95 -0
- package/dist/checks/specialized/image-metadata-validation.js.map +1 -0
- package/dist/checks/specialized/image-optimization.d.ts +16 -0
- package/dist/checks/specialized/image-optimization.d.ts.map +1 -0
- package/dist/checks/specialized/image-optimization.js +86 -0
- package/dist/checks/specialized/image-optimization.js.map +1 -0
- package/dist/checks/specialized/invalid-module-imports.d.ts +24 -0
- package/dist/checks/specialized/invalid-module-imports.d.ts.map +1 -0
- package/dist/checks/specialized/invalid-module-imports.js +209 -0
- package/dist/checks/specialized/invalid-module-imports.js.map +1 -0
- package/dist/checks/specialized/lint-validation.d.ts +26 -0
- package/dist/checks/specialized/lint-validation.d.ts.map +1 -0
- package/dist/checks/specialized/lint-validation.js +193 -0
- package/dist/checks/specialized/lint-validation.js.map +1 -0
- package/dist/checks/specialized/listing-workflow.d.ts +19 -0
- package/dist/checks/specialized/listing-workflow.d.ts.map +1 -0
- package/dist/checks/specialized/listing-workflow.js +89 -0
- package/dist/checks/specialized/listing-workflow.js.map +1 -0
- package/dist/checks/specialized/mui-imports-validation.d.ts +18 -0
- package/dist/checks/specialized/mui-imports-validation.d.ts.map +1 -0
- package/dist/checks/specialized/mui-imports-validation.js +134 -0
- package/dist/checks/specialized/mui-imports-validation.js.map +1 -0
- package/dist/checks/specialized/nextauth-v5-compliance.d.ts +16 -0
- package/dist/checks/specialized/nextauth-v5-compliance.d.ts.map +1 -0
- package/dist/checks/specialized/nextauth-v5-compliance.js +164 -0
- package/dist/checks/specialized/nextauth-v5-compliance.js.map +1 -0
- package/dist/checks/specialized/nextjs-params-check.d.ts +14 -0
- package/dist/checks/specialized/nextjs-params-check.d.ts.map +1 -0
- package/dist/checks/specialized/nextjs-params-check.js +140 -0
- package/dist/checks/specialized/nextjs-params-check.js.map +1 -0
- package/dist/checks/specialized/no-legacy-catalog-aliases-validation.d.ts +16 -0
- package/dist/checks/specialized/no-legacy-catalog-aliases-validation.d.ts.map +1 -0
- package/dist/checks/specialized/no-legacy-catalog-aliases-validation.js +36 -0
- package/dist/checks/specialized/no-legacy-catalog-aliases-validation.js.map +1 -0
- package/dist/checks/specialized/no-wata-cardgraded-validation.d.ts +22 -0
- package/dist/checks/specialized/no-wata-cardgraded-validation.d.ts.map +1 -0
- package/dist/checks/specialized/no-wata-cardgraded-validation.js +97 -0
- package/dist/checks/specialized/no-wata-cardgraded-validation.js.map +1 -0
- package/dist/checks/specialized/parameter-consistency-check.d.ts +20 -0
- package/dist/checks/specialized/parameter-consistency-check.d.ts.map +1 -0
- package/dist/checks/specialized/parameter-consistency-check.js +115 -0
- package/dist/checks/specialized/parameter-consistency-check.js.map +1 -0
- package/dist/checks/specialized/prisma-field-names-validation.d.ts +15 -0
- package/dist/checks/specialized/prisma-field-names-validation.d.ts.map +1 -0
- package/dist/checks/specialized/prisma-field-names-validation.js +35 -0
- package/dist/checks/specialized/prisma-field-names-validation.js.map +1 -0
- package/dist/checks/specialized/prisma-null-syntax.d.ts +34 -0
- package/dist/checks/specialized/prisma-null-syntax.d.ts.map +1 -0
- package/dist/checks/specialized/prisma-null-syntax.js +330 -0
- package/dist/checks/specialized/prisma-null-syntax.js.map +1 -0
- package/dist/checks/specialized/prisma-query-validation.d.ts +15 -0
- package/dist/checks/specialized/prisma-query-validation.d.ts.map +1 -0
- package/dist/checks/specialized/prisma-query-validation.js +35 -0
- package/dist/checks/specialized/prisma-query-validation.js.map +1 -0
- package/dist/checks/specialized/product-type-validation.d.ts +17 -0
- package/dist/checks/specialized/product-type-validation.d.ts.map +1 -0
- package/dist/checks/specialized/product-type-validation.js +129 -0
- package/dist/checks/specialized/product-type-validation.js.map +1 -0
- package/dist/checks/specialized/responsive-image-validation.d.ts +14 -0
- package/dist/checks/specialized/responsive-image-validation.d.ts.map +1 -0
- package/dist/checks/specialized/responsive-image-validation.js +101 -0
- package/dist/checks/specialized/responsive-image-validation.js.map +1 -0
- package/dist/checks/specialized/root-cleanliness.d.ts +21 -0
- package/dist/checks/specialized/root-cleanliness.d.ts.map +1 -0
- package/dist/checks/specialized/root-cleanliness.js +251 -0
- package/dist/checks/specialized/root-cleanliness.js.map +1 -0
- package/dist/checks/specialized/rotation-detection-validation.d.ts +16 -0
- package/dist/checks/specialized/rotation-detection-validation.d.ts.map +1 -0
- package/dist/checks/specialized/rotation-detection-validation.js +113 -0
- package/dist/checks/specialized/rotation-detection-validation.js.map +1 -0
- package/dist/checks/specialized/script-organization.d.ts +17 -0
- package/dist/checks/specialized/script-organization.d.ts.map +1 -0
- package/dist/checks/specialized/script-organization.js +487 -0
- package/dist/checks/specialized/script-organization.js.map +1 -0
- package/dist/checks/specialized/shared-components-migration.d.ts +137 -0
- package/dist/checks/specialized/shared-components-migration.d.ts.map +1 -0
- package/dist/checks/specialized/shared-components-migration.js +1288 -0
- package/dist/checks/specialized/shared-components-migration.js.map +1 -0
- package/dist/checks/specialized/store-specialties-normalization.d.ts +10 -0
- package/dist/checks/specialized/store-specialties-normalization.d.ts.map +1 -0
- package/dist/checks/specialized/store-specialties-normalization.js +126 -0
- package/dist/checks/specialized/store-specialties-normalization.js.map +1 -0
- package/dist/checks/specialized/two-stage-trim-validation.d.ts +16 -0
- package/dist/checks/specialized/two-stage-trim-validation.d.ts.map +1 -0
- package/dist/checks/specialized/two-stage-trim-validation.js +115 -0
- package/dist/checks/specialized/two-stage-trim-validation.js.map +1 -0
- package/dist/checks/specialized/underscore-variable-audit.d.ts +26 -0
- package/dist/checks/specialized/underscore-variable-audit.d.ts.map +1 -0
- package/dist/checks/specialized/underscore-variable-audit.js +219 -0
- package/dist/checks/specialized/underscore-variable-audit.js.map +1 -0
- package/dist/checks/specialized/unified-badge-consistency.d.ts +16 -0
- package/dist/checks/specialized/unified-badge-consistency.d.ts.map +1 -0
- package/dist/checks/specialized/unified-badge-consistency.js +284 -0
- package/dist/checks/specialized/unified-badge-consistency.js.map +1 -0
- package/dist/checks/specialized/validate-integration-enums.d.ts +15 -0
- package/dist/checks/specialized/validate-integration-enums.d.ts.map +1 -0
- package/dist/checks/specialized/validate-integration-enums.js +131 -0
- package/dist/checks/specialized/validate-integration-enums.js.map +1 -0
- package/dist/checks/testing/action-regression.d.ts +23 -0
- package/dist/checks/testing/action-regression.d.ts.map +1 -0
- package/dist/checks/testing/action-regression.js +192 -0
- package/dist/checks/testing/action-regression.js.map +1 -0
- package/dist/checks/testing/critical-api-coverage.d.ts +21 -0
- package/dist/checks/testing/critical-api-coverage.d.ts.map +1 -0
- package/dist/checks/testing/critical-api-coverage.js +158 -0
- package/dist/checks/testing/critical-api-coverage.js.map +1 -0
- package/dist/checks/testing/data-entry-regression-required.d.ts +24 -0
- package/dist/checks/testing/data-entry-regression-required.d.ts.map +1 -0
- package/dist/checks/testing/data-entry-regression-required.js +378 -0
- package/dist/checks/testing/data-entry-regression-required.js.map +1 -0
- package/dist/checks/testing/e2e-best-practices.d.ts +24 -0
- package/dist/checks/testing/e2e-best-practices.d.ts.map +1 -0
- package/dist/checks/testing/e2e-best-practices.js +791 -0
- package/dist/checks/testing/e2e-best-practices.js.map +1 -0
- package/dist/checks/testing/e2e-flake-patterns.d.ts +26 -0
- package/dist/checks/testing/e2e-flake-patterns.d.ts.map +1 -0
- package/dist/checks/testing/e2e-flake-patterns.js +305 -0
- package/dist/checks/testing/e2e-flake-patterns.js.map +1 -0
- package/dist/checks/testing/e2e-redundant-visibility-checks.d.ts +25 -0
- package/dist/checks/testing/e2e-redundant-visibility-checks.d.ts.map +1 -0
- package/dist/checks/testing/e2e-redundant-visibility-checks.js +613 -0
- package/dist/checks/testing/e2e-redundant-visibility-checks.js.map +1 -0
- package/dist/checks/testing/e2e-slow-tests.d.ts +9 -0
- package/dist/checks/testing/e2e-slow-tests.d.ts.map +1 -0
- package/dist/checks/testing/e2e-slow-tests.js +142 -0
- package/dist/checks/testing/e2e-slow-tests.js.map +1 -0
- package/dist/checks/testing/e2e-timeouts.d.ts +9 -0
- package/dist/checks/testing/e2e-timeouts.d.ts.map +1 -0
- package/dist/checks/testing/e2e-timeouts.js +82 -0
- package/dist/checks/testing/e2e-timeouts.js.map +1 -0
- package/dist/checks/testing/integration-e2e-depth.d.ts +20 -0
- package/dist/checks/testing/integration-e2e-depth.d.ts.map +1 -0
- package/dist/checks/testing/integration-e2e-depth.js +575 -0
- package/dist/checks/testing/integration-e2e-depth.js.map +1 -0
- package/dist/checks/testing/playwright-feature-coverage-gaps.d.ts +31 -0
- package/dist/checks/testing/playwright-feature-coverage-gaps.d.ts.map +1 -0
- package/dist/checks/testing/playwright-feature-coverage-gaps.js +1582 -0
- package/dist/checks/testing/playwright-feature-coverage-gaps.js.map +1 -0
- package/dist/checks/testing/playwright-mock-inventory.d.ts +24 -0
- package/dist/checks/testing/playwright-mock-inventory.d.ts.map +1 -0
- package/dist/checks/testing/playwright-mock-inventory.js +380 -0
- package/dist/checks/testing/playwright-mock-inventory.js.map +1 -0
- package/dist/checks/testing/test-coverage-threshold.d.ts +25 -0
- package/dist/checks/testing/test-coverage-threshold.d.ts.map +1 -0
- package/dist/checks/testing/test-coverage-threshold.js +166 -0
- package/dist/checks/testing/test-coverage-threshold.js.map +1 -0
- package/dist/checks/testing/test-flakiness-score.d.ts +27 -0
- package/dist/checks/testing/test-flakiness-score.d.ts.map +1 -0
- package/dist/checks/testing/test-flakiness-score.js +358 -0
- package/dist/checks/testing/test-flakiness-score.js.map +1 -0
- package/dist/checks/testing/test-patterns.d.ts +16 -0
- package/dist/checks/testing/test-patterns.d.ts.map +1 -0
- package/dist/checks/testing/test-patterns.js +156 -0
- package/dist/checks/testing/test-patterns.js.map +1 -0
- package/dist/checks/workflows/a-plus-rating-validation.d.ts +42 -0
- package/dist/checks/workflows/a-plus-rating-validation.d.ts.map +1 -0
- package/dist/checks/workflows/a-plus-rating-validation.js +527 -0
- package/dist/checks/workflows/a-plus-rating-validation.js.map +1 -0
- package/dist/checks/workflows/affected.d.ts +14 -0
- package/dist/checks/workflows/affected.d.ts.map +1 -0
- package/dist/checks/workflows/affected.js +126 -0
- package/dist/checks/workflows/affected.js.map +1 -0
- package/dist/checks/workflows/ai.d.ts +6 -0
- package/dist/checks/workflows/ai.d.ts.map +1 -0
- package/dist/checks/workflows/ai.js +42 -0
- package/dist/checks/workflows/ai.js.map +1 -0
- package/dist/checks/workflows/all.d.ts +31 -0
- package/dist/checks/workflows/all.d.ts.map +1 -0
- package/dist/checks/workflows/all.js +2688 -0
- package/dist/checks/workflows/all.js.map +1 -0
- package/dist/checks/workflows/commit.d.ts +19 -0
- package/dist/checks/workflows/commit.d.ts.map +1 -0
- package/dist/checks/workflows/commit.js +207 -0
- package/dist/checks/workflows/commit.js.map +1 -0
- package/dist/checks/workflows/critical.d.ts +9 -0
- package/dist/checks/workflows/critical.d.ts.map +1 -0
- package/dist/checks/workflows/critical.js +213 -0
- package/dist/checks/workflows/critical.js.map +1 -0
- package/dist/checks/workflows/database-id-validation.d.ts +9 -0
- package/dist/checks/workflows/database-id-validation.d.ts.map +1 -0
- package/dist/checks/workflows/database-id-validation.js +13 -0
- package/dist/checks/workflows/database-id-validation.js.map +1 -0
- package/dist/checks/workflows/deploy.d.ts +20 -0
- package/dist/checks/workflows/deploy.d.ts.map +1 -0
- package/dist/checks/workflows/deploy.js +107 -0
- package/dist/checks/workflows/deploy.js.map +1 -0
- package/dist/checks/workflows/deployment-readiness.d.ts +12 -0
- package/dist/checks/workflows/deployment-readiness.d.ts.map +1 -0
- package/dist/checks/workflows/deployment-readiness.js +403 -0
- package/dist/checks/workflows/deployment-readiness.js.map +1 -0
- package/dist/checks/workflows/dev.d.ts +19 -0
- package/dist/checks/workflows/dev.d.ts.map +1 -0
- package/dist/checks/workflows/dev.js +88 -0
- package/dist/checks/workflows/dev.js.map +1 -0
- package/dist/checks/workflows/development.d.ts +9 -0
- package/dist/checks/workflows/development.d.ts.map +1 -0
- package/dist/checks/workflows/development.js +65 -0
- package/dist/checks/workflows/development.js.map +1 -0
- package/dist/checks/workflows/enterprise.d.ts +10 -0
- package/dist/checks/workflows/enterprise.d.ts.map +1 -0
- package/dist/checks/workflows/enterprise.js +359 -0
- package/dist/checks/workflows/enterprise.js.map +1 -0
- package/dist/checks/workflows/images.d.ts +6 -0
- package/dist/checks/workflows/images.d.ts.map +1 -0
- package/dist/checks/workflows/images.js +58 -0
- package/dist/checks/workflows/images.js.map +1 -0
- package/dist/checks/workflows/naming.d.ts +19 -0
- package/dist/checks/workflows/naming.d.ts.map +1 -0
- package/dist/checks/workflows/naming.js +42 -0
- package/dist/checks/workflows/naming.js.map +1 -0
- package/dist/checks/workflows/performance.d.ts +8 -0
- package/dist/checks/workflows/performance.d.ts.map +1 -0
- package/dist/checks/workflows/performance.js +77 -0
- package/dist/checks/workflows/performance.js.map +1 -0
- package/dist/checks/workflows/pre-deploy.d.ts +6 -0
- package/dist/checks/workflows/pre-deploy.d.ts.map +1 -0
- package/dist/checks/workflows/pre-deploy.js +41 -0
- package/dist/checks/workflows/pre-deploy.js.map +1 -0
- package/dist/checks/workflows/security.d.ts +8 -0
- package/dist/checks/workflows/security.d.ts.map +1 -0
- package/dist/checks/workflows/security.js +71 -0
- package/dist/checks/workflows/security.js.map +1 -0
- package/dist/checks/workflows/supercatch.d.ts +8 -0
- package/dist/checks/workflows/supercatch.d.ts.map +1 -0
- package/dist/checks/workflows/supercatch.js +127 -0
- package/dist/checks/workflows/supercatch.js.map +1 -0
- package/dist/checks/workflows/ui-quality.d.ts +9 -0
- package/dist/checks/workflows/ui-quality.d.ts.map +1 -0
- package/dist/checks/workflows/ui-quality.js +264 -0
- package/dist/checks/workflows/ui-quality.js.map +1 -0
- package/dist/checks/workflows/ui-uniformity.d.ts +18 -0
- package/dist/checks/workflows/ui-uniformity.d.ts.map +1 -0
- package/dist/checks/workflows/ui-uniformity.js +265 -0
- package/dist/checks/workflows/ui-uniformity.js.map +1 -0
- package/dist/checks/workflows/vercel.d.ts +16 -0
- package/dist/checks/workflows/vercel.d.ts.map +1 -0
- package/dist/checks/workflows/vercel.js +173 -0
- package/dist/checks/workflows/vercel.js.map +1 -0
- package/dist/utils/validation-helpers.d.ts +43 -0
- package/dist/utils/validation-helpers.d.ts.map +1 -0
- package/dist/utils/validation-helpers.js +370 -0
- package/dist/utils/validation-helpers.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,2008 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Consolidated UI Components Preflight
|
|
5
|
+
*
|
|
6
|
+
* Combines component checks into one comprehensive module:
|
|
7
|
+
* - button-variant-consistency (button patterns, MUI ā new variants)
|
|
8
|
+
* - badge-overflow-validation (badge positioning)
|
|
9
|
+
* - icon-size-consistency (icon sizing)
|
|
10
|
+
* - border-radius-consistency (border radius tokens)
|
|
11
|
+
* - shadow-consistency (shadow tokens)
|
|
12
|
+
* - chip-consistency (chip height & patterns)
|
|
13
|
+
* - platform-icons (duplicate platform icon definitions)
|
|
14
|
+
* - numeric-spacing (hardcoded spacing values)
|
|
15
|
+
* - hardcoded-colors (internal colors should use tokens)
|
|
16
|
+
* - card-outline-consistency (cards should use shadows, not borders)
|
|
17
|
+
* - metric-grid-glass-variant (MetricGrid glass variant has unreliable borders) - BLOCKING
|
|
18
|
+
* - chained-css-variables (CSS variables that chain may not resolve) - BLOCKING
|
|
19
|
+
* - duplicate-classname-props (multiple className props overwrite each other) - BLOCKING
|
|
20
|
+
* - deprecated-system-components (ButtonSystem, FlexSystem, CardSystem, GridSystem, FeedbackSystem)
|
|
21
|
+
* - custom-stat-displays (Avatar + Typography patterns should use StatCardGrid)
|
|
22
|
+
*
|
|
23
|
+
* All issues are warnings (non-blocking) to allow gradual migration.
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* pnpm preflight:ui-components # All component checks
|
|
27
|
+
* pnpm preflight:ui-components buttons # Button patterns only
|
|
28
|
+
* pnpm preflight:ui-components badges # Badge overflow only
|
|
29
|
+
* pnpm preflight:ui-components icons # Icon sizes only
|
|
30
|
+
* pnpm preflight:ui-components radius # Border radius only
|
|
31
|
+
* pnpm preflight:ui-components shadows # Shadows only
|
|
32
|
+
* pnpm preflight:ui-components chips # Chip consistency only
|
|
33
|
+
* pnpm preflight:ui-components platforms # Platform icons only
|
|
34
|
+
* pnpm preflight:ui-components spacing # Numeric spacing only
|
|
35
|
+
* pnpm preflight:ui-components colors # Hardcoded colors only
|
|
36
|
+
* pnpm preflight:ui-components cards # Card outline consistency only
|
|
37
|
+
* pnpm preflight:ui-components metric-grid # MetricGrid glass variant check
|
|
38
|
+
* pnpm preflight:ui-components css-variables # Chained CSS variable check
|
|
39
|
+
* pnpm preflight:ui-components classname # Duplicate className props check
|
|
40
|
+
* pnpm preflight:ui-components deprecated # Deprecated System components only
|
|
41
|
+
* pnpm preflight:ui-components stat-displays # Custom stat display detection
|
|
42
|
+
*/
|
|
43
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
44
|
+
if (k2 === undefined) k2 = k;
|
|
45
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
46
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
47
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
48
|
+
}
|
|
49
|
+
Object.defineProperty(o, k2, desc);
|
|
50
|
+
}) : (function(o, m, k, k2) {
|
|
51
|
+
if (k2 === undefined) k2 = k;
|
|
52
|
+
o[k2] = m[k];
|
|
53
|
+
}));
|
|
54
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
55
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
56
|
+
}) : function(o, v) {
|
|
57
|
+
o["default"] = v;
|
|
58
|
+
});
|
|
59
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
60
|
+
var ownKeys = function(o) {
|
|
61
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
62
|
+
var ar = [];
|
|
63
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
64
|
+
return ar;
|
|
65
|
+
};
|
|
66
|
+
return ownKeys(o);
|
|
67
|
+
};
|
|
68
|
+
return function (mod) {
|
|
69
|
+
if (mod && mod.__esModule) return mod;
|
|
70
|
+
var result = {};
|
|
71
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
72
|
+
__setModuleDefault(result, mod);
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
})();
|
|
76
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
77
|
+
exports.UIComponentsPreflightModule = void 0;
|
|
78
|
+
const fs = __importStar(require("fs"));
|
|
79
|
+
const path = __importStar(require("path"));
|
|
80
|
+
const console_chars_1 = require("../../utils/console-chars");
|
|
81
|
+
const file_cache_1 = require("../../shared/file-cache");
|
|
82
|
+
const glob_patterns_1 = require("../../shared/glob-patterns");
|
|
83
|
+
const universal_progress_reporter_1 = require("../system/universal-progress-reporter");
|
|
84
|
+
const exclusions_1 = require("../../shared/exclusions");
|
|
85
|
+
const concurrency_config_1 = require("../../shared/concurrency-config");
|
|
86
|
+
// Check metadata
|
|
87
|
+
const id = "consolidated/ui-components";
|
|
88
|
+
// Essential defaults (node_modules, __mocks__, .next, dist, coverage)
|
|
89
|
+
const EXCLUDED = (0, glob_patterns_1.extendExcludes)(glob_patterns_1.STANDARD_EXCLUDES_WITH_TESTS, ["**/packages/ui/**"]);
|
|
90
|
+
// App-level exclusions loaded from config
|
|
91
|
+
let appExclusions = [];
|
|
92
|
+
// Platform brand colors (OK to hardcode - external brands)
|
|
93
|
+
const PLATFORM_BRAND_COLORS = [
|
|
94
|
+
"#E53238", // eBay
|
|
95
|
+
"#232F3E",
|
|
96
|
+
"#FF9900", // Amazon
|
|
97
|
+
"#FFC220",
|
|
98
|
+
"#0071CE", // Walmart
|
|
99
|
+
"#F16521", // Etsy
|
|
100
|
+
"#1A1A1A",
|
|
101
|
+
"#FF6B35", // Whatnot
|
|
102
|
+
"#00F2EA",
|
|
103
|
+
"#FE2C55", // TikTok
|
|
104
|
+
"#7AB55C", // Shopify
|
|
105
|
+
"#96588A", // WooCommerce
|
|
106
|
+
"#6772E5", // Shippo
|
|
107
|
+
"#3B5998", // ShipStation
|
|
108
|
+
].map((c) => c.toLowerCase());
|
|
109
|
+
// CACHED FILE LISTS - Scan once, use everywhere
|
|
110
|
+
let _cachedAppComponentsTsxFiles = null;
|
|
111
|
+
async function getAppComponentsTsxFiles() {
|
|
112
|
+
if (!_cachedAppComponentsTsxFiles) {
|
|
113
|
+
_cachedAppComponentsTsxFiles = await file_cache_1.fileCache.getAppAndComponentsTSX();
|
|
114
|
+
}
|
|
115
|
+
return _cachedAppComponentsTsxFiles;
|
|
116
|
+
}
|
|
117
|
+
// Get concurrency from shared config (respects PREFLIGHT_CONCURRENCY env var)
|
|
118
|
+
const concurrencyConfig = (0, concurrency_config_1.getConcurrencyConfig)();
|
|
119
|
+
class UIComponentsPreflightModule {
|
|
120
|
+
verbose;
|
|
121
|
+
parallel = false;
|
|
122
|
+
strictUniformity;
|
|
123
|
+
constructor(options = {}) {
|
|
124
|
+
this.verbose = options.verbose || false;
|
|
125
|
+
this.parallel = options.parallel || concurrencyConfig.parallel;
|
|
126
|
+
this.strictUniformity = process.env.PREFLIGHT_STRICT_UNIFORMITY === "1";
|
|
127
|
+
}
|
|
128
|
+
getLineNumberFromIndex(content, index) {
|
|
129
|
+
return content.slice(0, index).split("\n").length;
|
|
130
|
+
}
|
|
131
|
+
isLikelyInteractive(tag) {
|
|
132
|
+
return (/\bonClick=/.test(tag) ||
|
|
133
|
+
/\bonKeyDown=/.test(tag) ||
|
|
134
|
+
/\bonKeyUp=/.test(tag) ||
|
|
135
|
+
/\brole=\{?['"]button['"]\}?/.test(tag) ||
|
|
136
|
+
/\btabIndex=\{?0\}?/.test(tag));
|
|
137
|
+
}
|
|
138
|
+
async getComponentFiles() {
|
|
139
|
+
const files = await file_cache_1.fileCache.getComponentsTsxJsxFiles();
|
|
140
|
+
return files.filter((file) => !(0, exclusions_1.shouldExcludeFile)(file, appExclusions));
|
|
141
|
+
}
|
|
142
|
+
async getAllFiles() {
|
|
143
|
+
const files = await file_cache_1.fileCache.getAppAndComponentsTSX();
|
|
144
|
+
return files.filter((file) => !(0, exclusions_1.shouldExcludeFile)(file, appExclusions));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* UnifiedDialog padding guardrail
|
|
148
|
+
* Ensures the shared UnifiedDialog enforces internal padding so dialog content
|
|
149
|
+
* can't regress to flush-to-border when underlying dialog primitives change.
|
|
150
|
+
*/
|
|
151
|
+
async checkUnifiedDialogPadding() {
|
|
152
|
+
const startTime = Date.now();
|
|
153
|
+
const issues = [];
|
|
154
|
+
const file = "components/shared/unified-dialog/UnifiedDialog.tsx";
|
|
155
|
+
try {
|
|
156
|
+
const content = fs.readFileSync(file, "utf8");
|
|
157
|
+
const titleHasPadding = /<DialogTitle[^>]*\bclassName\s*=\s*"[^"]*\bp-\d+[^"]*"/m.test(content);
|
|
158
|
+
const contentHasPadding = /<DialogContent[^>]*\bclassName\s*=\s*"[^"]*\bp-\d+[^"]*"/m.test(content);
|
|
159
|
+
if (!titleHasPadding || !contentHasPadding) {
|
|
160
|
+
issues.push({
|
|
161
|
+
file,
|
|
162
|
+
line: 1,
|
|
163
|
+
type: "unified-dialog-padding",
|
|
164
|
+
severity: "warning",
|
|
165
|
+
message: "UnifiedDialog should enforce internal padding on DialogTitle and DialogContent to prevent flush-to-border dialogs.",
|
|
166
|
+
suggestion: "Add a padding className (e.g., p-6) to <DialogTitle> and <DialogContent> in UnifiedDialog.",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
issues.push({
|
|
172
|
+
file,
|
|
173
|
+
line: 1,
|
|
174
|
+
type: "unified-dialog-padding",
|
|
175
|
+
severity: "warning",
|
|
176
|
+
message: "Could not read UnifiedDialog implementation to validate padding guardrail.",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
name: "UnifiedDialog padding guardrail",
|
|
181
|
+
passed: issues.length === 0,
|
|
182
|
+
blocking: true,
|
|
183
|
+
issues,
|
|
184
|
+
duration: Date.now() - startTime,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Button Variant Consistency
|
|
189
|
+
* Detects MUI-style variants that should use new button system
|
|
190
|
+
*/
|
|
191
|
+
async checkButtons() {
|
|
192
|
+
const startTime = Date.now();
|
|
193
|
+
const issues = [];
|
|
194
|
+
const files = await this.getAllFiles();
|
|
195
|
+
for (const file of files) {
|
|
196
|
+
const content = fs.readFileSync(file, "utf8");
|
|
197
|
+
const lines = content.split("\n");
|
|
198
|
+
// Normalize path separators for cross-platform compatibility
|
|
199
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
200
|
+
const isUploadSurface = normalizedFile.includes("components/upload/") ||
|
|
201
|
+
normalizedFile.includes("components/shared/file/");
|
|
202
|
+
// components/ui contains the implementation of the design system primitives
|
|
203
|
+
// (it is expected to use native elements internally).
|
|
204
|
+
const isUIPrimitive = normalizedFile.includes("components/ui/");
|
|
205
|
+
lines.forEach((line, index) => {
|
|
206
|
+
if (line.trim().startsWith("//"))
|
|
207
|
+
return;
|
|
208
|
+
// MUI variant="contained" ā variant="default"
|
|
209
|
+
if (/variant=["']contained["']/i.test(line)) {
|
|
210
|
+
issues.push({
|
|
211
|
+
file,
|
|
212
|
+
line: index + 1,
|
|
213
|
+
type: "mui-contained-variant",
|
|
214
|
+
severity: "warning",
|
|
215
|
+
message: 'MUI variant="contained" should be variant="default"',
|
|
216
|
+
suggestion: 'Change to variant="default" for consistency',
|
|
217
|
+
snippet: line.trim().substring(0, 80),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
// MUI variant="outlined" ā variant="outline"
|
|
221
|
+
if (/variant=["']outlined["']/i.test(line) && /<Button/i.test(line)) {
|
|
222
|
+
issues.push({
|
|
223
|
+
file,
|
|
224
|
+
line: index + 1,
|
|
225
|
+
type: "mui-outlined-variant",
|
|
226
|
+
severity: "warning",
|
|
227
|
+
message: 'MUI variant="outlined" should be variant="outline"',
|
|
228
|
+
suggestion: 'Change to variant="outline" for consistency',
|
|
229
|
+
snippet: line.trim().substring(0, 80),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
// Native button usage (upload surfaces should use shared UI components)
|
|
233
|
+
if (isUploadSurface && /<button\b/.test(line)) {
|
|
234
|
+
issues.push({
|
|
235
|
+
file,
|
|
236
|
+
line: index + 1,
|
|
237
|
+
type: "native-button-upload-surface",
|
|
238
|
+
severity: "warning",
|
|
239
|
+
message: "Native <button> used in upload UI",
|
|
240
|
+
suggestion: "Use <Button> or <IconButton> from @/components/ui for consistent spacing/typography",
|
|
241
|
+
snippet: line.trim().substring(0, 80),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
// Native button usage (global): encourage consistent use of design-system buttons everywhere.
|
|
245
|
+
// Keep this warning out of the primitives folder to avoid noise.
|
|
246
|
+
if (!isUIPrimitive && !isUploadSurface && /<button\b/.test(line)) {
|
|
247
|
+
issues.push({
|
|
248
|
+
file,
|
|
249
|
+
line: index + 1,
|
|
250
|
+
type: "native-button-global",
|
|
251
|
+
severity: "warning",
|
|
252
|
+
message: "Native <button> used outside the UI system",
|
|
253
|
+
suggestion: "Prefer <Button> / <IconButton> from @/components/ui to keep spacing and typography uniform",
|
|
254
|
+
snippet: line.trim().substring(0, 80),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
// Native button with inline styles
|
|
258
|
+
if (/<button[^>]*style=/.test(line)) {
|
|
259
|
+
issues.push({
|
|
260
|
+
file,
|
|
261
|
+
line: index + 1,
|
|
262
|
+
type: "native-button-styles",
|
|
263
|
+
severity: "warning",
|
|
264
|
+
message: "Native <button> with inline styles",
|
|
265
|
+
suggestion: "Use <Button> from @/components/ui",
|
|
266
|
+
snippet: line.trim().substring(0, 80),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
// Button with bg className
|
|
270
|
+
if (/<Button[^>]*className=["'][^"']*bg-/i.test(line)) {
|
|
271
|
+
issues.push({
|
|
272
|
+
file,
|
|
273
|
+
line: index + 1,
|
|
274
|
+
type: "button-bg-class",
|
|
275
|
+
severity: "warning",
|
|
276
|
+
message: "Button using className for background",
|
|
277
|
+
suggestion: "Use variant prop instead",
|
|
278
|
+
snippet: line.trim().substring(0, 80),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
// Check for Button asChild with Link that might have centering issues
|
|
282
|
+
// The Button component should handle this, but warn if custom styles override
|
|
283
|
+
if (/<Button[^>]*asChild/.test(line)) {
|
|
284
|
+
const context = lines.slice(index, Math.min(lines.length, index + 5)).join("\n");
|
|
285
|
+
// Check if Link has styles that might break centering
|
|
286
|
+
if (context.includes("style=") && context.includes("<Link")) {
|
|
287
|
+
// Look for height or display overrides that could affect centering
|
|
288
|
+
if (/style=\{[^}]*(height|display|alignItems|justifyContent)/.test(context)) {
|
|
289
|
+
issues.push({
|
|
290
|
+
file,
|
|
291
|
+
line: index + 1,
|
|
292
|
+
type: "button-asChild-style-override",
|
|
293
|
+
severity: "info",
|
|
294
|
+
message: "Button asChild with style overrides on Link",
|
|
295
|
+
suggestion: "Ensure Link styles don't break text centering (height, display, alignItems, justifyContent)",
|
|
296
|
+
snippet: line.trim().substring(0, 80),
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
name: "Button Variant Consistency",
|
|
305
|
+
passed: true,
|
|
306
|
+
blocking: true,
|
|
307
|
+
issues,
|
|
308
|
+
duration: Date.now() - startTime,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Link Styled As Button
|
|
313
|
+
* Detects <Link>/<a> elements that are visually styled like buttons.
|
|
314
|
+
* Prefer <Button asChild> for consistent spacing/typography.
|
|
315
|
+
*/
|
|
316
|
+
async checkLinkButtons() {
|
|
317
|
+
const startTime = Date.now();
|
|
318
|
+
const issues = [];
|
|
319
|
+
const files = await this.getAllFiles();
|
|
320
|
+
for (const file of files) {
|
|
321
|
+
// UI primitives may legitimately render anchors/buttons internally
|
|
322
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
323
|
+
if (normalizedFile.includes("components/ui/"))
|
|
324
|
+
continue;
|
|
325
|
+
const content = fs.readFileSync(file, "utf8");
|
|
326
|
+
// Match opening tags for Link or a (can span multiple lines)
|
|
327
|
+
const tagMatches = content.matchAll(/<(Link|a)\b[\s\S]*?>/g);
|
|
328
|
+
for (const match of tagMatches) {
|
|
329
|
+
const fullTag = match[0];
|
|
330
|
+
const startIndex = match.index ?? 0;
|
|
331
|
+
// Ignore if already wrapped via Button asChild in the same opening tag context
|
|
332
|
+
// (Not perfect, but avoids common false positives)
|
|
333
|
+
if (fullTag.includes("asChild") && fullTag.includes("<Button"))
|
|
334
|
+
continue;
|
|
335
|
+
const hasClassName = /className=\{?["']/.test(fullTag);
|
|
336
|
+
const hasStyle = /\bstyle=\{/.test(fullTag);
|
|
337
|
+
if (!hasClassName && !hasStyle)
|
|
338
|
+
continue;
|
|
339
|
+
// Skip accessibility skip-links (sr-only pattern)
|
|
340
|
+
if (/sr-only/.test(fullTag))
|
|
341
|
+
continue;
|
|
342
|
+
// Skip navigation card/tile patterns (flex-col with items-center = card layout, not button)
|
|
343
|
+
// These are semantically links for navigation, not action buttons
|
|
344
|
+
if (/flex-col/.test(fullTag) && /items-center/.test(fullTag))
|
|
345
|
+
continue;
|
|
346
|
+
// Skip grid-based category/navigation tiles
|
|
347
|
+
if (/grid/.test(fullTag) || /text-center/.test(fullTag))
|
|
348
|
+
continue;
|
|
349
|
+
// Heuristic: if it has padding + button-like affordances, it's likely a button.
|
|
350
|
+
// (We keep this intentionally conservative to avoid noise.)
|
|
351
|
+
const looksLikeButton = /\b(px|py|p)-\d+\b/.test(fullTag) &&
|
|
352
|
+
(/(bg-|border|rounded|shadow|hover:|focus:|inline-flex|items-center|justify-center)/.test(fullTag) ||
|
|
353
|
+
/\bvariant=\{?['"](?:default|outline|ghost|text)['"]\}?/.test(fullTag));
|
|
354
|
+
if (!looksLikeButton)
|
|
355
|
+
continue;
|
|
356
|
+
issues.push({
|
|
357
|
+
file,
|
|
358
|
+
line: this.getLineNumberFromIndex(content, startIndex),
|
|
359
|
+
type: "link-styled-as-button",
|
|
360
|
+
severity: "warning",
|
|
361
|
+
message: "<Link>/<a> styled like a button",
|
|
362
|
+
suggestion: "Wrap the Link with <Button asChild> from @/components/ui for uniform button spacing/typography",
|
|
363
|
+
snippet: fullTag.replace(/\s+/g, " ").trim().substring(0, 120),
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
name: "Link Styled As Button",
|
|
369
|
+
passed: true,
|
|
370
|
+
blocking: true,
|
|
371
|
+
issues,
|
|
372
|
+
duration: Date.now() - startTime,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Clickable Non-Button Elements
|
|
377
|
+
* Detects <div>/<span> used as interactive controls.
|
|
378
|
+
* Prefer <Button>/<IconButton> for consistent spacing/typography and a11y.
|
|
379
|
+
*/
|
|
380
|
+
async checkClickableNonButtons() {
|
|
381
|
+
const startTime = Date.now();
|
|
382
|
+
const issues = [];
|
|
383
|
+
const files = await this.getAllFiles();
|
|
384
|
+
for (const file of files) {
|
|
385
|
+
// Normalize path separators for cross-platform compatibility
|
|
386
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
387
|
+
if (normalizedFile.includes("components/ui/"))
|
|
388
|
+
continue;
|
|
389
|
+
const content = fs.readFileSync(file, "utf8");
|
|
390
|
+
// Match opening tags for div/span (can span multiple lines)
|
|
391
|
+
const tagMatches = content.matchAll(/<(div|span)\b[\s\S]*?>/g);
|
|
392
|
+
for (const match of tagMatches) {
|
|
393
|
+
const tag = match[0];
|
|
394
|
+
const startIndex = match.index ?? 0;
|
|
395
|
+
if (!this.isLikelyInteractive(tag))
|
|
396
|
+
continue;
|
|
397
|
+
// Skip clearly non-control patterns
|
|
398
|
+
if (/\brole=\{?['"]presentation['"]\}?/.test(tag))
|
|
399
|
+
continue;
|
|
400
|
+
if (/\baria-hidden=\{?['"]true['"]\}?/.test(tag))
|
|
401
|
+
continue;
|
|
402
|
+
// Heuristic: treat as "button-like" if it advertises interactivity
|
|
403
|
+
const hasPointerCue = /cursor-pointer/.test(tag);
|
|
404
|
+
const hasButtonRole = /\brole=\{?['"]button['"]\}?/.test(tag);
|
|
405
|
+
if (!hasPointerCue && !hasButtonRole)
|
|
406
|
+
continue;
|
|
407
|
+
issues.push({
|
|
408
|
+
file,
|
|
409
|
+
line: this.getLineNumberFromIndex(content, startIndex),
|
|
410
|
+
type: "clickable-non-button",
|
|
411
|
+
severity: "warning",
|
|
412
|
+
message: "Clickable <div>/<span> used as a control",
|
|
413
|
+
suggestion: "Use <Button> / <IconButton> from @/components/ui (or <Button asChild>) for uniform behavior and accessibility",
|
|
414
|
+
snippet: tag.replace(/\s+/g, " ").trim().substring(0, 120),
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
name: "Clickable Non-Buttons",
|
|
420
|
+
passed: true,
|
|
421
|
+
blocking: true,
|
|
422
|
+
issues,
|
|
423
|
+
duration: Date.now() - startTime,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* IconButton Accessibility
|
|
428
|
+
* Ensures IconButton has an accessible label (label / aria-label / title).
|
|
429
|
+
*/
|
|
430
|
+
async checkIconButtonAccessibility() {
|
|
431
|
+
const startTime = Date.now();
|
|
432
|
+
const issues = [];
|
|
433
|
+
const files = await this.getAllFiles();
|
|
434
|
+
for (const file of files) {
|
|
435
|
+
// UI primitives define IconButton itself
|
|
436
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
437
|
+
if (normalizedFile.includes("components/ui/"))
|
|
438
|
+
continue;
|
|
439
|
+
const content = fs.readFileSync(file, "utf8");
|
|
440
|
+
// Find all IconButton opening tags - need to handle JSX expressions inside props
|
|
441
|
+
// which may contain > characters (e.g., icon={<Trash2 size={16} />})
|
|
442
|
+
const iconButtonStarts = [...content.matchAll(/<IconButton\b/g)];
|
|
443
|
+
for (const startMatch of iconButtonStarts) {
|
|
444
|
+
const startIndex = startMatch.index ?? 0;
|
|
445
|
+
// Find the closing > of this tag, accounting for nested JSX in props
|
|
446
|
+
let depth = 0;
|
|
447
|
+
let inString = false;
|
|
448
|
+
let stringChar = "";
|
|
449
|
+
let tagEnd = startIndex;
|
|
450
|
+
for (let i = startIndex; i < content.length; i++) {
|
|
451
|
+
const char = content[i];
|
|
452
|
+
const prevChar = i > 0 ? content[i - 1] : "";
|
|
453
|
+
// Track string boundaries
|
|
454
|
+
if ((char === '"' || char === "'" || char === "`") && prevChar !== "\\") {
|
|
455
|
+
if (!inString) {
|
|
456
|
+
inString = true;
|
|
457
|
+
stringChar = char;
|
|
458
|
+
}
|
|
459
|
+
else if (char === stringChar) {
|
|
460
|
+
inString = false;
|
|
461
|
+
}
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (inString)
|
|
465
|
+
continue;
|
|
466
|
+
// Track JSX expression depth
|
|
467
|
+
if (char === "{") {
|
|
468
|
+
depth++;
|
|
469
|
+
}
|
|
470
|
+
else if (char === "}") {
|
|
471
|
+
depth--;
|
|
472
|
+
}
|
|
473
|
+
else if (char === ">" && depth === 0) {
|
|
474
|
+
tagEnd = i + 1;
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
const tag = content.slice(startIndex, tagEnd);
|
|
479
|
+
// Check for accessibility props
|
|
480
|
+
const hasLabel = /\blabel=/.test(tag);
|
|
481
|
+
const hasAria = /\baria-label=/.test(tag);
|
|
482
|
+
const hasTitle = /\btitle=/.test(tag);
|
|
483
|
+
if (hasLabel || hasAria || hasTitle)
|
|
484
|
+
continue;
|
|
485
|
+
issues.push({
|
|
486
|
+
file,
|
|
487
|
+
line: this.getLineNumberFromIndex(content, startIndex),
|
|
488
|
+
type: "iconbutton-missing-label",
|
|
489
|
+
severity: "warning",
|
|
490
|
+
message: "IconButton missing accessibility label",
|
|
491
|
+
suggestion: 'Add label="..." (preferred) or aria-label="..."',
|
|
492
|
+
snippet: tag.replace(/\s+/g, " ").trim().substring(0, 120),
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
name: "IconButton Accessibility",
|
|
498
|
+
passed: true,
|
|
499
|
+
blocking: true,
|
|
500
|
+
issues,
|
|
501
|
+
duration: Date.now() - startTime,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Badge Overflow Validation
|
|
506
|
+
*/
|
|
507
|
+
async checkBadges() {
|
|
508
|
+
const startTime = Date.now();
|
|
509
|
+
const issues = [];
|
|
510
|
+
const files = await getAppComponentsTsxFiles();
|
|
511
|
+
for (const file of files) {
|
|
512
|
+
const content = fs.readFileSync(file, "utf8");
|
|
513
|
+
const lines = content.split("\n");
|
|
514
|
+
lines.forEach((line, index) => {
|
|
515
|
+
// Badge with negative top position
|
|
516
|
+
if (/<(Chip|Badge|div)[^>]*position:\s*["']absolute["'][^>]*top:\s*-\d+/i.test(line)) {
|
|
517
|
+
const context = lines
|
|
518
|
+
.slice(Math.max(0, index - 10), Math.min(lines.length, index + 10))
|
|
519
|
+
.join("\n");
|
|
520
|
+
if (!context.includes('overflow: "visible"') &&
|
|
521
|
+
!context.includes("overflow: 'visible'")) {
|
|
522
|
+
issues.push({
|
|
523
|
+
file,
|
|
524
|
+
line: index + 1,
|
|
525
|
+
type: "badge-overflow",
|
|
526
|
+
severity: "warning",
|
|
527
|
+
message: "Badge with negative top without overflow: visible",
|
|
528
|
+
suggestion: 'Add overflow: "visible" to parent',
|
|
529
|
+
snippet: line.trim().substring(0, 80),
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
name: "Badge Overflow Validation",
|
|
537
|
+
passed: true,
|
|
538
|
+
blocking: true,
|
|
539
|
+
issues,
|
|
540
|
+
duration: Date.now() - startTime,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Icon Size Consistency
|
|
545
|
+
*/
|
|
546
|
+
async checkIcons() {
|
|
547
|
+
const startTime = Date.now();
|
|
548
|
+
const issues = [];
|
|
549
|
+
const files = await this.getAllFiles();
|
|
550
|
+
for (const file of files) {
|
|
551
|
+
const content = fs.readFileSync(file, "utf8");
|
|
552
|
+
const lines = content.split("\n");
|
|
553
|
+
lines.forEach((line, index) => {
|
|
554
|
+
if (line.trim().startsWith("//"))
|
|
555
|
+
return;
|
|
556
|
+
// Hardcoded icon size (non-standard)
|
|
557
|
+
const sizeMatch = line.match(/size=\{?(\d+)\}?/);
|
|
558
|
+
if (sizeMatch && (line.includes("lucide-react") || line.includes("Icon"))) {
|
|
559
|
+
const size = parseInt(sizeMatch[1]);
|
|
560
|
+
if (![16, 20, 24, 32].includes(size)) {
|
|
561
|
+
issues.push({
|
|
562
|
+
file,
|
|
563
|
+
line: index + 1,
|
|
564
|
+
type: "hardcoded-icon-size",
|
|
565
|
+
severity: "info",
|
|
566
|
+
message: `Non-standard icon size: ${size}`,
|
|
567
|
+
suggestion: "Use 16, 20, 24, or 32",
|
|
568
|
+
snippet: line.trim().substring(0, 80),
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
name: "Icon Size Consistency",
|
|
576
|
+
passed: true,
|
|
577
|
+
blocking: true,
|
|
578
|
+
issues,
|
|
579
|
+
duration: Date.now() - startTime,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Border Radius Consistency
|
|
584
|
+
*/
|
|
585
|
+
async checkBorderRadius() {
|
|
586
|
+
const startTime = Date.now();
|
|
587
|
+
const issues = [];
|
|
588
|
+
const files = await this.getAllFiles();
|
|
589
|
+
for (const file of files) {
|
|
590
|
+
const content = fs.readFileSync(file, "utf8");
|
|
591
|
+
const lines = content.split("\n");
|
|
592
|
+
lines.forEach((line, index) => {
|
|
593
|
+
if (line.trim().startsWith("//"))
|
|
594
|
+
return;
|
|
595
|
+
// Hardcoded border-radius (numeric)
|
|
596
|
+
if (/borderRadius:\s*(\d+)\s*[,}]/i.test(line) && !line.includes("var(--radius-")) {
|
|
597
|
+
issues.push({
|
|
598
|
+
file,
|
|
599
|
+
line: index + 1,
|
|
600
|
+
type: "hardcoded-radius-numeric",
|
|
601
|
+
severity: "warning",
|
|
602
|
+
message: "Hardcoded numeric border-radius",
|
|
603
|
+
suggestion: "Use var(--radius-sm|md|lg|xl)",
|
|
604
|
+
snippet: line.trim().substring(0, 80),
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
// Hardcoded border-radius with px/rem
|
|
608
|
+
if (/borderRadius:\s*['"]?(\d+px|\d+rem)['"]?/i.test(line) &&
|
|
609
|
+
!line.includes("var(--radius-")) {
|
|
610
|
+
issues.push({
|
|
611
|
+
file,
|
|
612
|
+
line: index + 1,
|
|
613
|
+
type: "hardcoded-radius",
|
|
614
|
+
severity: "warning",
|
|
615
|
+
message: "Hardcoded border-radius",
|
|
616
|
+
suggestion: "Use var(--radius-sm|md|lg|xl)",
|
|
617
|
+
snippet: line.trim().substring(0, 80),
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
// Tailwind rounded classes
|
|
621
|
+
const rounded = line.match(/\brounded-(sm|md|lg|xl|2xl|full)\b/);
|
|
622
|
+
if (rounded) {
|
|
623
|
+
issues.push({
|
|
624
|
+
file,
|
|
625
|
+
line: index + 1,
|
|
626
|
+
type: "tailwind-rounded",
|
|
627
|
+
severity: "info",
|
|
628
|
+
message: `Tailwind ${rounded[0]} class`,
|
|
629
|
+
suggestion: `Use style={{ borderRadius: "var(--radius-${rounded[1]})" }}`,
|
|
630
|
+
snippet: line.trim().substring(0, 80),
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
return {
|
|
636
|
+
name: "Border Radius Consistency",
|
|
637
|
+
passed: true,
|
|
638
|
+
blocking: true,
|
|
639
|
+
issues,
|
|
640
|
+
duration: Date.now() - startTime,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Shadow Consistency
|
|
645
|
+
*/
|
|
646
|
+
async checkShadows() {
|
|
647
|
+
const startTime = Date.now();
|
|
648
|
+
const issues = [];
|
|
649
|
+
const files = await this.getAllFiles();
|
|
650
|
+
for (const file of files) {
|
|
651
|
+
const content = fs.readFileSync(file, "utf8");
|
|
652
|
+
const lines = content.split("\n");
|
|
653
|
+
lines.forEach((line, index) => {
|
|
654
|
+
if (line.trim().startsWith("//"))
|
|
655
|
+
return;
|
|
656
|
+
// Hardcoded box-shadow
|
|
657
|
+
const shadowMatch = line.match(/boxShadow:\s*['"]([^'"]+)['"]?/i);
|
|
658
|
+
if (shadowMatch &&
|
|
659
|
+
!line.includes("var(--shadow-") &&
|
|
660
|
+
!line.includes("var(--glass-shadow)")) {
|
|
661
|
+
const value = shadowMatch[1];
|
|
662
|
+
if (value !== "none" && value !== "inherit") {
|
|
663
|
+
issues.push({
|
|
664
|
+
file,
|
|
665
|
+
line: index + 1,
|
|
666
|
+
type: "hardcoded-shadow",
|
|
667
|
+
severity: "warning",
|
|
668
|
+
message: "Hardcoded box-shadow",
|
|
669
|
+
suggestion: "Use var(--shadow-sm|md|lg|xl)",
|
|
670
|
+
snippet: line.trim().substring(0, 80),
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
// Tailwind shadow classes - but exclude hover: patterns (those are valid CSS interactions)
|
|
675
|
+
// Also exclude if already using shadow-[var(--shadow-xl)] syntax (correct pattern)
|
|
676
|
+
// Also exclude if inside var(--shadow-*) token usage
|
|
677
|
+
const tailwindShadow = line.match(/(?<!hover:)(?<!focus:)(?<!active:)(?<!var\(--)\bshadow-(sm|md|lg|xl|2xl)\b/);
|
|
678
|
+
if (tailwindShadow &&
|
|
679
|
+
!line.includes("shadow-[var(--shadow-") &&
|
|
680
|
+
!line.includes("var(--shadow-")) {
|
|
681
|
+
issues.push({
|
|
682
|
+
file,
|
|
683
|
+
line: index + 1,
|
|
684
|
+
type: "tailwind-shadow",
|
|
685
|
+
severity: "warning",
|
|
686
|
+
message: `Tailwind ${tailwindShadow[0]} class`,
|
|
687
|
+
suggestion: `Use style={{ boxShadow: "var(--shadow-${tailwindShadow[1]})" }}`,
|
|
688
|
+
snippet: line.trim().substring(0, 80),
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
name: "Shadow Consistency",
|
|
695
|
+
passed: true,
|
|
696
|
+
blocking: true,
|
|
697
|
+
issues,
|
|
698
|
+
duration: Date.now() - startTime,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Chip Height Consistency
|
|
703
|
+
* Ensures all chips use consistent heights via StatusChip or proper size props
|
|
704
|
+
*/
|
|
705
|
+
async checkChips() {
|
|
706
|
+
const startTime = Date.now();
|
|
707
|
+
const issues = [];
|
|
708
|
+
const allFiles = await getAppComponentsTsxFiles();
|
|
709
|
+
for (const file of allFiles) {
|
|
710
|
+
const content = fs.readFileSync(file, "utf8");
|
|
711
|
+
const lines = content.split("\n");
|
|
712
|
+
// Check if file uses StatusChip (preferred for status indicators)
|
|
713
|
+
const usesStatusChip = content.includes("StatusChip") || content.includes("CategoryChip");
|
|
714
|
+
lines.forEach((line, index) => {
|
|
715
|
+
if (line.trim().startsWith("//"))
|
|
716
|
+
return;
|
|
717
|
+
// Check for Chip with custom height overrides
|
|
718
|
+
if (/<Chip[^>]*sx=\{[^}]*height:/i.test(line)) {
|
|
719
|
+
issues.push({
|
|
720
|
+
file,
|
|
721
|
+
line: index + 1,
|
|
722
|
+
type: "chip-custom-height",
|
|
723
|
+
severity: "info",
|
|
724
|
+
message: "Chip with custom height override",
|
|
725
|
+
suggestion: 'Use size="sm" (20px) or size="md" (24px), or use StatusChip for status indicators',
|
|
726
|
+
snippet: line.trim().substring(0, 80),
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
// Check for status-like Chip that should use StatusChip
|
|
730
|
+
if (/<Chip[^>]*label=["'](Active|Inactive|Pending|Available|Coming Soon|Connected|Beta|Draft|Published)/i.test(line) &&
|
|
731
|
+
!usesStatusChip) {
|
|
732
|
+
issues.push({
|
|
733
|
+
file,
|
|
734
|
+
line: index + 1,
|
|
735
|
+
type: "chip-status-pattern",
|
|
736
|
+
severity: "info",
|
|
737
|
+
message: "Status indicator using Chip instead of StatusChip",
|
|
738
|
+
suggestion: 'Use <StatusChip status="active" /> for consistent status indicators',
|
|
739
|
+
snippet: line.trim().substring(0, 80),
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
// Check for Chip without explicit size prop (inconsistent sizing)
|
|
743
|
+
if (/<Chip[^>]*label=/i.test(line) &&
|
|
744
|
+
!/<Chip[^>]*size=/i.test(line) &&
|
|
745
|
+
!line.includes("StatusChip")) {
|
|
746
|
+
issues.push({
|
|
747
|
+
file,
|
|
748
|
+
line: index + 1,
|
|
749
|
+
type: "chip-no-size",
|
|
750
|
+
severity: "info",
|
|
751
|
+
message: "Chip without explicit size prop",
|
|
752
|
+
suggestion: 'Add size="sm" or size="md" for consistent sizing',
|
|
753
|
+
snippet: line.trim().substring(0, 80),
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
return {
|
|
759
|
+
name: "Chip Consistency",
|
|
760
|
+
passed: true,
|
|
761
|
+
blocking: true,
|
|
762
|
+
issues,
|
|
763
|
+
duration: Date.now() - startTime,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Platform Icons Consistency
|
|
768
|
+
* Detects duplicate platform icon definitions that should use PlatformIcon component
|
|
769
|
+
*/
|
|
770
|
+
async checkPlatformIcons() {
|
|
771
|
+
const startTime = Date.now();
|
|
772
|
+
const issues = [];
|
|
773
|
+
const allFiles = await getAppComponentsTsxFiles();
|
|
774
|
+
// Platform brand colors that indicate inline platform icons
|
|
775
|
+
const platformPatterns = [
|
|
776
|
+
{ pattern: /#E53238/i, platform: "eBay" },
|
|
777
|
+
{ pattern: /#232F3E.*#FF9900|#FF9900.*#232F3E/i, platform: "Amazon" },
|
|
778
|
+
{ pattern: /#FFC220.*#0071CE|#0071CE.*#FFC220/i, platform: "Walmart" },
|
|
779
|
+
{ pattern: /#F16521/i, platform: "Etsy" },
|
|
780
|
+
{ pattern: /#1A1A1A.*#FF6B35|#FF6B35.*#1A1A1A/i, platform: "Whatnot" },
|
|
781
|
+
{ pattern: /#00F2EA|#FE2C55/i, platform: "TikTok" },
|
|
782
|
+
{ pattern: /#7AB55C/i, platform: "Shopify" },
|
|
783
|
+
{ pattern: /#96588A/i, platform: "WooCommerce" },
|
|
784
|
+
];
|
|
785
|
+
for (const file of allFiles) {
|
|
786
|
+
const content = fs.readFileSync(file, "utf8");
|
|
787
|
+
// Skip if file imports PlatformIcon
|
|
788
|
+
if (content.includes("PlatformIcon"))
|
|
789
|
+
continue;
|
|
790
|
+
const lines = content.split("\n");
|
|
791
|
+
lines.forEach((line, index) => {
|
|
792
|
+
if (line.trim().startsWith("//"))
|
|
793
|
+
return;
|
|
794
|
+
// Check for inline platform icon patterns
|
|
795
|
+
for (const { pattern, platform } of platformPatterns) {
|
|
796
|
+
if (pattern.test(line) &&
|
|
797
|
+
(line.includes("Avatar") || line.includes("bgcolor") || line.includes("Box"))) {
|
|
798
|
+
issues.push({
|
|
799
|
+
file,
|
|
800
|
+
line: index + 1,
|
|
801
|
+
type: "inline-platform-icon",
|
|
802
|
+
severity: "info",
|
|
803
|
+
message: `Inline ${platform} icon definition`,
|
|
804
|
+
suggestion: `Use <PlatformIcon platform="${platform.toLowerCase()}" /> for consistency`,
|
|
805
|
+
snippet: line.trim().substring(0, 80),
|
|
806
|
+
});
|
|
807
|
+
break; // Only report once per line
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
return {
|
|
813
|
+
name: "Platform Icons",
|
|
814
|
+
passed: true,
|
|
815
|
+
blocking: true,
|
|
816
|
+
issues,
|
|
817
|
+
duration: Date.now() - startTime,
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Numeric Spacing Consistency
|
|
822
|
+
* Detects hardcoded numeric spacing values that should use design tokens
|
|
823
|
+
*/
|
|
824
|
+
async checkNumericSpacing() {
|
|
825
|
+
const startTime = Date.now();
|
|
826
|
+
const issues = [];
|
|
827
|
+
const allFiles = await getAppComponentsTsxFiles();
|
|
828
|
+
for (const file of allFiles) {
|
|
829
|
+
const content = fs.readFileSync(file, "utf8");
|
|
830
|
+
const lines = content.split("\n");
|
|
831
|
+
lines.forEach((line, index) => {
|
|
832
|
+
if (line.trim().startsWith("//"))
|
|
833
|
+
return;
|
|
834
|
+
if (line.includes("var(--spacing"))
|
|
835
|
+
return; // Already using tokens
|
|
836
|
+
// Check for numeric margin/padding values > 0.5 (allow 0, 0.5 for small adjustments)
|
|
837
|
+
const spacingMatch = line.match(/\b(m[btlrxy]?|p[btlrxy]?):\s*(\d+\.?\d*)\b/);
|
|
838
|
+
if (spacingMatch) {
|
|
839
|
+
const value = parseFloat(spacingMatch[2]);
|
|
840
|
+
if (value > 0.5) {
|
|
841
|
+
issues.push({
|
|
842
|
+
file,
|
|
843
|
+
line: index + 1,
|
|
844
|
+
type: "numeric-spacing",
|
|
845
|
+
severity: "info",
|
|
846
|
+
message: `Numeric spacing value: ${spacingMatch[1]}: ${value}`,
|
|
847
|
+
suggestion: `Use ${spacingMatch[1]}: "var(--spacing-${Math.round(value * 2)})" for consistency`,
|
|
848
|
+
snippet: line.trim().substring(0, 80),
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
// Check for gap values
|
|
853
|
+
const gapMatch = line.match(/\bgap:\s*(\d+\.?\d*)\b/);
|
|
854
|
+
if (gapMatch) {
|
|
855
|
+
const value = parseFloat(gapMatch[1]);
|
|
856
|
+
if (value > 0.5) {
|
|
857
|
+
issues.push({
|
|
858
|
+
file,
|
|
859
|
+
line: index + 1,
|
|
860
|
+
type: "numeric-gap",
|
|
861
|
+
severity: "info",
|
|
862
|
+
message: `Numeric gap value: ${value}`,
|
|
863
|
+
suggestion: `Use gap: "var(--spacing-${Math.round(value * 2)})" for consistency`,
|
|
864
|
+
snippet: line.trim().substring(0, 80),
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
return {
|
|
871
|
+
name: "Numeric Spacing",
|
|
872
|
+
passed: true,
|
|
873
|
+
blocking: true,
|
|
874
|
+
issues,
|
|
875
|
+
duration: Date.now() - startTime,
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Card Outline Consistency
|
|
880
|
+
* Detects cards/containers using borders instead of shadows for elevation
|
|
881
|
+
*/
|
|
882
|
+
async checkCardOutlines() {
|
|
883
|
+
const startTime = Date.now();
|
|
884
|
+
const issues = [];
|
|
885
|
+
const allFiles = await this.getAllFiles();
|
|
886
|
+
// Patterns that indicate a card-like element with a border (problematic)
|
|
887
|
+
const borderPatterns = [
|
|
888
|
+
// Box with border: "1px solid" and borderRadius
|
|
889
|
+
/border:\s*["']1px solid["']/i,
|
|
890
|
+
];
|
|
891
|
+
// Elements where borders are appropriate (not cards)
|
|
892
|
+
const excludePatterns = [
|
|
893
|
+
/variant=["']outlined["']/i,
|
|
894
|
+
/dashed/i, // Upload zones
|
|
895
|
+
/<TextField/i,
|
|
896
|
+
/<Select/i,
|
|
897
|
+
/<Input/i,
|
|
898
|
+
/<Checkbox/i,
|
|
899
|
+
/<Radio/i,
|
|
900
|
+
/Divider/i,
|
|
901
|
+
/borderTop:/i, // Single-side borders are OK (dividers)
|
|
902
|
+
/borderBottom:/i,
|
|
903
|
+
/borderLeft:/i,
|
|
904
|
+
/borderRight:/i,
|
|
905
|
+
];
|
|
906
|
+
for (const file of allFiles) {
|
|
907
|
+
const content = fs.readFileSync(file, "utf8");
|
|
908
|
+
const lines = content.split("\n");
|
|
909
|
+
lines.forEach((line, index) => {
|
|
910
|
+
if (line.trim().startsWith("//"))
|
|
911
|
+
return;
|
|
912
|
+
// Check if line has border pattern
|
|
913
|
+
const hasBorder = borderPatterns.some((p) => p.test(line));
|
|
914
|
+
if (!hasBorder)
|
|
915
|
+
return;
|
|
916
|
+
// Skip if it's an excluded element type
|
|
917
|
+
const isExcluded = excludePatterns.some((p) => p.test(line));
|
|
918
|
+
if (isExcluded)
|
|
919
|
+
return;
|
|
920
|
+
// Check surrounding context for card-like indicators
|
|
921
|
+
const contextStart = Math.max(0, index - 5);
|
|
922
|
+
const contextEnd = Math.min(lines.length, index + 5);
|
|
923
|
+
const context = lines.slice(contextStart, contextEnd).join("\n");
|
|
924
|
+
// Only flag if it has significant padding (card-like) and borderRadius
|
|
925
|
+
const hasSignificantPadding = /p:\s*["']var\(--spacing-[4-9]\)|padding.*var\(--spacing-[4-9]\)/i.test(context);
|
|
926
|
+
const hasBorderRadius = /borderRadius/i.test(context);
|
|
927
|
+
if (hasSignificantPadding && hasBorderRadius) {
|
|
928
|
+
issues.push({
|
|
929
|
+
file,
|
|
930
|
+
line: index + 1,
|
|
931
|
+
type: "card-border-instead-of-shadow",
|
|
932
|
+
severity: "warning",
|
|
933
|
+
message: "Card-like element using border instead of shadow",
|
|
934
|
+
suggestion: "Use boxShadow for card elevation instead of border for visual consistency",
|
|
935
|
+
snippet: line.trim().substring(0, 80),
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
return {
|
|
941
|
+
name: "Card Outline Consistency",
|
|
942
|
+
passed: true,
|
|
943
|
+
blocking: true,
|
|
944
|
+
issues,
|
|
945
|
+
duration: Date.now() - startTime,
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Card Background Check
|
|
950
|
+
* Detects Card components using glass/transparent backgrounds instead of solid backgrounds.
|
|
951
|
+
* Cards should have a solid background (var(--bg-primary) or white) for proper visual hierarchy.
|
|
952
|
+
*
|
|
953
|
+
* ${emoji.error} BAD: background: "var(--glass-bg-medium)" - transparent, hard to see
|
|
954
|
+
* ${emoji.success} GOOD: background: "var(--bg-primary)" - solid white/dark background
|
|
955
|
+
*
|
|
956
|
+
* NOTE: Some glass backgrounds are intentional for design (login forms, loading skeletons, etc.)
|
|
957
|
+
*/
|
|
958
|
+
async checkCardBackground() {
|
|
959
|
+
const startTime = Date.now();
|
|
960
|
+
const issues = [];
|
|
961
|
+
const allFiles = await this.getAllFiles();
|
|
962
|
+
// Patterns that indicate transparent/glass backgrounds on cards
|
|
963
|
+
const glassBackgroundPatterns = [
|
|
964
|
+
/background:\s*["']var\(--glass-bg/i,
|
|
965
|
+
/backgroundColor:\s*["']var\(--glass-bg/i,
|
|
966
|
+
/background:\s*["']rgba\([^)]+,\s*0\.[0-3]\)/i, // rgba with very low opacity (0.0-0.3)
|
|
967
|
+
];
|
|
968
|
+
// Files/patterns where glass backgrounds are intentional
|
|
969
|
+
const excludePatterns = [
|
|
970
|
+
/Modal|Dialog|Drawer/i, // Modals can use glass
|
|
971
|
+
/Overlay/i,
|
|
972
|
+
/Tooltip/i,
|
|
973
|
+
/Popover/i,
|
|
974
|
+
/Header|Nav|Sidebar/i, // Navigation elements
|
|
975
|
+
/backdrop/i,
|
|
976
|
+
/Login|Auth|Register/i, // Auth pages often use glass for design
|
|
977
|
+
/Loading|Skeleton/i, // Loading states
|
|
978
|
+
/Filter|Search/i, // Filter panels
|
|
979
|
+
/Hero|Landing/i, // Landing page sections
|
|
980
|
+
/Faq|Help/i, // Help pages with gradient CTAs
|
|
981
|
+
];
|
|
982
|
+
for (const file of allFiles) {
|
|
983
|
+
const content = fs.readFileSync(file, "utf8");
|
|
984
|
+
const lines = content.split("\n");
|
|
985
|
+
// Skip files that are intentionally using glass effects
|
|
986
|
+
const fileName = file.split(/[/\\]/).pop() || "";
|
|
987
|
+
if (excludePatterns.some((p) => p.test(fileName)))
|
|
988
|
+
continue;
|
|
989
|
+
lines.forEach((line, index) => {
|
|
990
|
+
if (line.trim().startsWith("//"))
|
|
991
|
+
return;
|
|
992
|
+
// Check if line has glass background pattern
|
|
993
|
+
const hasGlassBackground = glassBackgroundPatterns.some((p) => p.test(line));
|
|
994
|
+
if (!hasGlassBackground)
|
|
995
|
+
return;
|
|
996
|
+
// Check surrounding context for Card component
|
|
997
|
+
const contextStart = Math.max(0, index - 10);
|
|
998
|
+
const contextEnd = Math.min(lines.length, index + 3);
|
|
999
|
+
const context = lines.slice(contextStart, contextEnd).join("\n");
|
|
1000
|
+
// Only flag if it's inside a Card component (not just any Box with glass)
|
|
1001
|
+
const isInsideCard = /<Card[^>]*>/.test(context) &&
|
|
1002
|
+
!/<\/Card>/.test(lines.slice(contextStart, index).join("\n"));
|
|
1003
|
+
// Also check for card-like patterns (MetricCard, StatCard, etc.)
|
|
1004
|
+
const isCardComponent = /Card[^>]*style=\{/.test(context) || /function\s+\w*Card/.test(context);
|
|
1005
|
+
// Skip if this is just an icon container or small decorative element
|
|
1006
|
+
const isIconContainer = /padding:\s*["']var\(--spacing-[23]\)["']/.test(context) &&
|
|
1007
|
+
/Icon|size=\{?\d+\}?|lucide-react/.test(context);
|
|
1008
|
+
const isSmallElement = /width:\s*\d+px|height:\s*\d+px/.test(context) &&
|
|
1009
|
+
parseInt((context.match(/(?:width|height):\s*(\d+)px/) || [])[1] || "100") < 80;
|
|
1010
|
+
const isInlineIconBox = /display:\s*["']inline-flex["']/.test(context) &&
|
|
1011
|
+
/borderRadius:\s*["']var\(--radius-full\)["']/.test(context);
|
|
1012
|
+
if ((isInsideCard || isCardComponent) &&
|
|
1013
|
+
!isIconContainer &&
|
|
1014
|
+
!isSmallElement &&
|
|
1015
|
+
!isInlineIconBox) {
|
|
1016
|
+
issues.push({
|
|
1017
|
+
file,
|
|
1018
|
+
line: index + 1,
|
|
1019
|
+
type: "card-glass-background",
|
|
1020
|
+
severity: "warning",
|
|
1021
|
+
message: "Card using glass/transparent background instead of solid background",
|
|
1022
|
+
suggestion: 'Use background: "var(--bg-primary)" for solid card backgrounds',
|
|
1023
|
+
snippet: line.trim().substring(0, 80),
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
return {
|
|
1029
|
+
name: "Card Background Check",
|
|
1030
|
+
passed: issues.length === 0,
|
|
1031
|
+
blocking: true,
|
|
1032
|
+
issues,
|
|
1033
|
+
duration: Date.now() - startTime,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Card Missing Background Check
|
|
1038
|
+
* Detects Card components that don't have a proper solid background.
|
|
1039
|
+
* Cards should always have a visible background for proper visual hierarchy.
|
|
1040
|
+
*
|
|
1041
|
+
* This check catches:
|
|
1042
|
+
* - Cards with transparent/none background
|
|
1043
|
+
* - Cards with only border but no background
|
|
1044
|
+
* - Card-like containers (product cards, item cards) missing backgrounds
|
|
1045
|
+
*
|
|
1046
|
+
* ${emoji.error} BAD: <Card style={{ background: "transparent" }}>
|
|
1047
|
+
* ${emoji.error} BAD: <Card className="bg-transparent">
|
|
1048
|
+
* ${emoji.success} GOOD: <Card> (uses default bg-[var(--bg-primary)])
|
|
1049
|
+
* ${emoji.success} GOOD: <Card className="bg-secondary">
|
|
1050
|
+
*/
|
|
1051
|
+
async checkCardMissingBackground() {
|
|
1052
|
+
const startTime = Date.now();
|
|
1053
|
+
const issues = [];
|
|
1054
|
+
const allFiles = await this.getAllFiles();
|
|
1055
|
+
// Patterns that indicate missing/transparent backgrounds
|
|
1056
|
+
const missingBackgroundPatterns = [
|
|
1057
|
+
/background:\s*["'](?:transparent|none)["']/i,
|
|
1058
|
+
/backgroundColor:\s*["'](?:transparent|none)["']/i,
|
|
1059
|
+
/\bbg-transparent\b/,
|
|
1060
|
+
/\bbg-none\b/,
|
|
1061
|
+
];
|
|
1062
|
+
// Files/patterns where transparent cards might be intentional
|
|
1063
|
+
const excludePatterns = [
|
|
1064
|
+
/Modal|Dialog|Drawer/i,
|
|
1065
|
+
/Overlay/i,
|
|
1066
|
+
/Tooltip/i,
|
|
1067
|
+
/Popover/i,
|
|
1068
|
+
/Skeleton/i,
|
|
1069
|
+
/Ghost/i, // Ghost buttons/cards
|
|
1070
|
+
/Placeholder/i,
|
|
1071
|
+
];
|
|
1072
|
+
for (const file of allFiles) {
|
|
1073
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1074
|
+
const lines = content.split("\n");
|
|
1075
|
+
// Skip files that are intentionally using transparent effects
|
|
1076
|
+
const fileName = file.split(/[/\\]/).pop() || "";
|
|
1077
|
+
if (excludePatterns.some((p) => p.test(fileName)))
|
|
1078
|
+
continue;
|
|
1079
|
+
lines.forEach((line, index) => {
|
|
1080
|
+
if (line.trim().startsWith("//"))
|
|
1081
|
+
return;
|
|
1082
|
+
// Check if line has transparent background pattern
|
|
1083
|
+
const hasMissingBackground = missingBackgroundPatterns.some((p) => p.test(line));
|
|
1084
|
+
if (!hasMissingBackground)
|
|
1085
|
+
return;
|
|
1086
|
+
// Check surrounding context for Card component
|
|
1087
|
+
const contextStart = Math.max(0, index - 10);
|
|
1088
|
+
const contextEnd = Math.min(lines.length, index + 3);
|
|
1089
|
+
const context = lines.slice(contextStart, contextEnd).join("\n");
|
|
1090
|
+
// Only flag if it's inside a Card component
|
|
1091
|
+
const isInsideCard = /<Card[^>]*>/.test(context) &&
|
|
1092
|
+
!/<\/Card>/.test(lines.slice(contextStart, index).join("\n"));
|
|
1093
|
+
const isCardComponent = /Card[^>]*(?:style=\{|className=)/.test(context) || /function\s+\w*Card/.test(context);
|
|
1094
|
+
// Also check for card-like patterns in class names
|
|
1095
|
+
const isCardLikeElement = /similar-item-card|product-card|listing-card|item-card/i.test(context);
|
|
1096
|
+
if (isInsideCard || isCardComponent || isCardLikeElement) {
|
|
1097
|
+
issues.push({
|
|
1098
|
+
file,
|
|
1099
|
+
line: index + 1,
|
|
1100
|
+
type: "card-missing-background",
|
|
1101
|
+
severity: "warning",
|
|
1102
|
+
message: "Card using transparent/none background - cards should have solid backgrounds",
|
|
1103
|
+
suggestion: "Remove transparent background or use bg-secondary/bg-[var(--bg-primary)] for proper visual hierarchy",
|
|
1104
|
+
snippet: line.trim().substring(0, 80),
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
return {
|
|
1110
|
+
name: "Card Missing Background Check",
|
|
1111
|
+
passed: issues.length === 0,
|
|
1112
|
+
blocking: false, // Warning for now
|
|
1113
|
+
issues,
|
|
1114
|
+
duration: Date.now() - startTime,
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Deprecated System Components
|
|
1119
|
+
* Detects usage of legacy System components that should be migrated to @supercatch/ui
|
|
1120
|
+
*/
|
|
1121
|
+
async checkDeprecatedComponents() {
|
|
1122
|
+
const startTime = Date.now();
|
|
1123
|
+
const issues = [];
|
|
1124
|
+
const allFiles = await this.getAllFiles();
|
|
1125
|
+
const DEPRECATED_IMPORTS = [
|
|
1126
|
+
{
|
|
1127
|
+
pattern: /@\/components\/ButtonSystem/,
|
|
1128
|
+
name: "ButtonSystem",
|
|
1129
|
+
replacement: "Button from @/components/ui",
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
pattern: /@\/components\/FlexSystem/,
|
|
1133
|
+
name: "FlexSystem",
|
|
1134
|
+
replacement: "Box or Stack from @/components/ui",
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
pattern: /@\/components\/CardSystem/,
|
|
1138
|
+
name: "CardSystem",
|
|
1139
|
+
replacement: "Card/CardContent from @/components/ui",
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
pattern: /@\/components\/GridSystem/,
|
|
1143
|
+
name: "GridSystem",
|
|
1144
|
+
replacement: "Box with CSS Grid from @/components/ui",
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
pattern: /@\/components\/FeedbackSystem/,
|
|
1148
|
+
name: "FeedbackSystem",
|
|
1149
|
+
replacement: "Alert/Chip from @/components/ui",
|
|
1150
|
+
},
|
|
1151
|
+
];
|
|
1152
|
+
for (const file of allFiles) {
|
|
1153
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1154
|
+
const lines = content.split("\n");
|
|
1155
|
+
lines.forEach((line, index) => {
|
|
1156
|
+
for (const deprecated of DEPRECATED_IMPORTS) {
|
|
1157
|
+
if (deprecated.pattern.test(line)) {
|
|
1158
|
+
issues.push({
|
|
1159
|
+
file,
|
|
1160
|
+
line: index + 1,
|
|
1161
|
+
type: "deprecated-system-component",
|
|
1162
|
+
severity: "warning",
|
|
1163
|
+
message: `Deprecated ${deprecated.name} import`,
|
|
1164
|
+
suggestion: `Migrate to ${deprecated.replacement}. See docs/SYSTEM-COMPONENTS-MIGRATION.md`,
|
|
1165
|
+
snippet: line.trim().substring(0, 80),
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
return {
|
|
1172
|
+
name: "Deprecated System Components",
|
|
1173
|
+
passed: issues.length === 0,
|
|
1174
|
+
blocking: true, // Warning only for now, can be made blocking later
|
|
1175
|
+
issues,
|
|
1176
|
+
duration: Date.now() - startTime,
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Custom Stat Display Detection
|
|
1181
|
+
*
|
|
1182
|
+
* Detects custom stat/metric displays that should use StatCardGrid instead.
|
|
1183
|
+
*
|
|
1184
|
+
* Patterns detected:
|
|
1185
|
+
* - Avatar + Typography (h4/h5/h6) in Card = custom stat display
|
|
1186
|
+
* - Grid of Cards with Avatar + number = custom stat grid
|
|
1187
|
+
* - Box with icon + Typography value pattern
|
|
1188
|
+
*
|
|
1189
|
+
* These should use StatCardGrid for consistency.
|
|
1190
|
+
*/
|
|
1191
|
+
async checkCustomStatDisplays() {
|
|
1192
|
+
const startTime = Date.now();
|
|
1193
|
+
const issues = [];
|
|
1194
|
+
const allFiles = await this.getAllFiles();
|
|
1195
|
+
// Skip files that already use StatCardGrid
|
|
1196
|
+
const SKIP_PATTERNS = [
|
|
1197
|
+
/StatCardGrid/,
|
|
1198
|
+
/StatCard/,
|
|
1199
|
+
/\.stories\./,
|
|
1200
|
+
/\.test\./,
|
|
1201
|
+
/\.spec\./,
|
|
1202
|
+
/packages\/ui/,
|
|
1203
|
+
];
|
|
1204
|
+
// Files that are allowed to have custom stat displays (the StatCard component itself, etc.)
|
|
1205
|
+
const ALLOWED_FILES = [
|
|
1206
|
+
"StatCard.tsx",
|
|
1207
|
+
"StatCard.examples.tsx",
|
|
1208
|
+
"MetricCard.tsx",
|
|
1209
|
+
"KpiCard.tsx",
|
|
1210
|
+
];
|
|
1211
|
+
for (const file of allFiles) {
|
|
1212
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
1213
|
+
// Skip allowed files
|
|
1214
|
+
if (ALLOWED_FILES.some((allowed) => normalizedFile.endsWith(allowed)))
|
|
1215
|
+
continue;
|
|
1216
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1217
|
+
// Skip if file already uses StatCardGrid
|
|
1218
|
+
if (SKIP_PATTERNS.some((p) => p.test(content)))
|
|
1219
|
+
continue;
|
|
1220
|
+
const lines = content.split("\n");
|
|
1221
|
+
// Pattern 1: Avatar inside Card with Typography h4/h5/h6 (stat value pattern)
|
|
1222
|
+
// This is the pattern we found in the image-mapping page
|
|
1223
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1224
|
+
const line = lines[i];
|
|
1225
|
+
// Look for Card opening
|
|
1226
|
+
if (/<Card[\s>]/.test(line) || /<Card\s+/.test(line)) {
|
|
1227
|
+
// Get the next 30 lines to check the Card content
|
|
1228
|
+
const cardBlock = lines.slice(i, Math.min(i + 30, lines.length)).join("\n");
|
|
1229
|
+
// Check for Avatar + Typography with large variant (stat display pattern)
|
|
1230
|
+
const hasAvatar = /<Avatar/.test(cardBlock);
|
|
1231
|
+
const hasStatTypography = /variant=["'](h4|h5)["']/.test(cardBlock);
|
|
1232
|
+
const hasValuePattern = /\.toLocaleString|formatCurrency|formatNumber|formatFileSize/.test(cardBlock);
|
|
1233
|
+
// Check for Stack direction="row" with Avatar (horizontal stat layout)
|
|
1234
|
+
const hasHorizontalStatLayout = /<Stack[^>]*direction=["']row["'][^>]*>[\s\S]*?<Avatar/.test(cardBlock);
|
|
1235
|
+
// Check if this looks like a stat card (Avatar + large number)
|
|
1236
|
+
const looksLikeStatCard = hasAvatar && (hasStatTypography || hasValuePattern) && hasHorizontalStatLayout;
|
|
1237
|
+
if (looksLikeStatCard) {
|
|
1238
|
+
// Additional check: is this in a Grid with multiple similar Cards?
|
|
1239
|
+
const surroundingContext = lines
|
|
1240
|
+
.slice(Math.max(0, i - 10), Math.min(lines.length, i + 40))
|
|
1241
|
+
.join("\n");
|
|
1242
|
+
const isInGrid = /<Grid[^>]*container/.test(surroundingContext);
|
|
1243
|
+
const multipleCards = (surroundingContext.match(/<Card[\s>]/g) || []).length >= 3;
|
|
1244
|
+
if (isInGrid && multipleCards) {
|
|
1245
|
+
issues.push({
|
|
1246
|
+
file,
|
|
1247
|
+
line: i + 1,
|
|
1248
|
+
type: "custom-stat-display",
|
|
1249
|
+
severity: "warning",
|
|
1250
|
+
message: "Custom stat display pattern detected (Avatar + Typography in Card grid)",
|
|
1251
|
+
suggestion: "Use StatCardGrid from @/components/shared for consistent stat displays. See AdminAnalyticsClient.tsx for example.",
|
|
1252
|
+
snippet: line.trim().substring(0, 80),
|
|
1253
|
+
});
|
|
1254
|
+
// Skip ahead to avoid duplicate warnings for same grid
|
|
1255
|
+
i += 30;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
// Pattern 2: Box with colored background + icon + Typography (inline stat pattern)
|
|
1261
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1262
|
+
const line = lines[i];
|
|
1263
|
+
// Look for Box with background color containing an icon
|
|
1264
|
+
if (/<Box[^>]*(?:bg-|backgroundColor)/.test(line) ||
|
|
1265
|
+
(/<Box/.test(line) &&
|
|
1266
|
+
/className=["'][^"']*bg-(?:blue|green|red|yellow|cyan|purple)/.test(line))) {
|
|
1267
|
+
const contextBlock = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
|
|
1268
|
+
// Check for icon + Typography pattern
|
|
1269
|
+
const hasIcon = /lucide-react|Icon|<[A-Z][a-z]+Icon/.test(contextBlock);
|
|
1270
|
+
const hasTypographyValue = /variant=["'](h4|h5|h6)["']/.test(contextBlock);
|
|
1271
|
+
const hasNumericValue = /\.toLocaleString|formatCurrency|formatNumber|%/.test(contextBlock);
|
|
1272
|
+
if (hasIcon && hasTypographyValue && hasNumericValue) {
|
|
1273
|
+
// Check if this is part of a repeated pattern (multiple stats)
|
|
1274
|
+
const fileContent = content;
|
|
1275
|
+
const similarPatterns = (fileContent.match(/<Box[^>]*(?:bg-|backgroundColor)[^>]*>[\s\S]*?<\/Box>/g) || []).length;
|
|
1276
|
+
if (similarPatterns >= 3) {
|
|
1277
|
+
issues.push({
|
|
1278
|
+
file,
|
|
1279
|
+
line: i + 1,
|
|
1280
|
+
type: "custom-stat-box",
|
|
1281
|
+
severity: "info",
|
|
1282
|
+
message: "Custom stat box pattern detected (colored Box + icon + value)",
|
|
1283
|
+
suggestion: "Consider using StatCardGrid for consistent stat displays across the app.",
|
|
1284
|
+
snippet: line.trim().substring(0, 80),
|
|
1285
|
+
});
|
|
1286
|
+
// Skip ahead
|
|
1287
|
+
i += 15;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
// Pattern 3: CardContent with direct stat layout (no CardHeader, just value + label)
|
|
1293
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1294
|
+
const line = lines[i];
|
|
1295
|
+
if (/<CardContent/.test(line)) {
|
|
1296
|
+
const cardContentBlock = lines.slice(i, Math.min(i + 20, lines.length)).join("\n");
|
|
1297
|
+
// Check for stat pattern: Stack with Avatar/icon + Box with h4/h5 + body2
|
|
1298
|
+
const hasStatStack = /<Stack[^>]*direction=["']row["'][^>]*spacing=\{?\d/.test(cardContentBlock);
|
|
1299
|
+
const hasIconOrAvatar = /<Avatar|<[A-Z][a-z]+\s/.test(cardContentBlock);
|
|
1300
|
+
const hasValueLabel = /variant=["'](h4|h5)["'][\s\S]*?variant=["'](body2|caption)["']/.test(cardContentBlock);
|
|
1301
|
+
// Check if this is in a Grid with multiple Cards
|
|
1302
|
+
const surroundingContext = lines
|
|
1303
|
+
.slice(Math.max(0, i - 15), Math.min(lines.length, i + 25))
|
|
1304
|
+
.join("\n");
|
|
1305
|
+
const isInStatGrid = /<Grid[^>]*container[^>]*spacing/.test(surroundingContext);
|
|
1306
|
+
const hasMultipleGridItems = (surroundingContext.match(/<Grid[^>]*xs=\{?12[^>]*sm=\{?6|<Grid[^>]*md=\{?2\.4/g) || [])
|
|
1307
|
+
.length >= 3;
|
|
1308
|
+
if (hasStatStack &&
|
|
1309
|
+
hasIconOrAvatar &&
|
|
1310
|
+
hasValueLabel &&
|
|
1311
|
+
isInStatGrid &&
|
|
1312
|
+
hasMultipleGridItems) {
|
|
1313
|
+
issues.push({
|
|
1314
|
+
file,
|
|
1315
|
+
line: i + 1,
|
|
1316
|
+
type: "custom-stat-card-content",
|
|
1317
|
+
severity: "warning",
|
|
1318
|
+
message: "Custom stat card pattern detected (CardContent with icon + value + label in grid)",
|
|
1319
|
+
suggestion: "Use StatCardGrid from @/components/shared. It provides consistent styling, loading states, and trend indicators.",
|
|
1320
|
+
snippet: line.trim().substring(0, 80),
|
|
1321
|
+
});
|
|
1322
|
+
// Skip the rest of this grid
|
|
1323
|
+
i += 20;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return {
|
|
1329
|
+
name: "Custom Stat Display Detection",
|
|
1330
|
+
passed: true, // Non-blocking, just warnings
|
|
1331
|
+
blocking: false,
|
|
1332
|
+
issues,
|
|
1333
|
+
duration: Date.now() - startTime,
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* MetricGrid Glass Variant Check (BLOCKING)
|
|
1338
|
+
*
|
|
1339
|
+
* Detects usage of MetricGrid with variant="glass" which has unreliable border rendering.
|
|
1340
|
+
* Use StatCardGrid instead for consistent KPI/stat displays with proper borders.
|
|
1341
|
+
*
|
|
1342
|
+
* ${emoji.error} BAD: <MetricGrid variant="glass" metrics={...} />
|
|
1343
|
+
* ${emoji.success} GOOD: <StatCardGrid stats={...} />
|
|
1344
|
+
*/
|
|
1345
|
+
async checkMetricGridGlassVariant() {
|
|
1346
|
+
const startTime = Date.now();
|
|
1347
|
+
const issues = [];
|
|
1348
|
+
const allFiles = await this.getAllFiles();
|
|
1349
|
+
for (const file of allFiles) {
|
|
1350
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1351
|
+
const lines = content.split("\n");
|
|
1352
|
+
lines.forEach((line, index) => {
|
|
1353
|
+
if (line.trim().startsWith("//"))
|
|
1354
|
+
return;
|
|
1355
|
+
// Check for MetricGrid with glass variant
|
|
1356
|
+
if (/MetricGrid/.test(line) && /variant=["']glass["']/.test(line)) {
|
|
1357
|
+
issues.push({
|
|
1358
|
+
file,
|
|
1359
|
+
line: index + 1,
|
|
1360
|
+
type: "metric-grid-glass-variant",
|
|
1361
|
+
severity: "error",
|
|
1362
|
+
message: 'MetricGrid with variant="glass" has unreliable border rendering',
|
|
1363
|
+
suggestion: "Use StatCardGrid from @/components/shared instead. It uses the Card component with proper borders.",
|
|
1364
|
+
snippet: line.trim().substring(0, 100),
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
// Also check for multi-line MetricGrid with glass variant
|
|
1368
|
+
if (/MetricGrid/.test(line) && !/>/.test(line)) {
|
|
1369
|
+
const contextEnd = Math.min(lines.length, index + 5);
|
|
1370
|
+
const context = lines.slice(index, contextEnd).join("\n");
|
|
1371
|
+
if (/variant=["']glass["']/.test(context)) {
|
|
1372
|
+
issues.push({
|
|
1373
|
+
file,
|
|
1374
|
+
line: index + 1,
|
|
1375
|
+
type: "metric-grid-glass-variant",
|
|
1376
|
+
severity: "error",
|
|
1377
|
+
message: 'MetricGrid with variant="glass" has unreliable border rendering',
|
|
1378
|
+
suggestion: "Use StatCardGrid from @/components/shared instead. It uses the Card component with proper borders.",
|
|
1379
|
+
snippet: line.trim().substring(0, 100),
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1386
|
+
return {
|
|
1387
|
+
name: "MetricGrid Glass Variant Check",
|
|
1388
|
+
passed: !hasErrors,
|
|
1389
|
+
blocking: true,
|
|
1390
|
+
issues,
|
|
1391
|
+
duration: Date.now() - startTime,
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Chained CSS Variable Check (BLOCKING)
|
|
1396
|
+
*
|
|
1397
|
+
* Detects usage of CSS variables that chain through intermediate variables,
|
|
1398
|
+
* which can fail to resolve in inline styles.
|
|
1399
|
+
*
|
|
1400
|
+
* ${emoji.error} BAD: border: "1px solid var(--glass-surface-border)" (chains to --border-color)
|
|
1401
|
+
* ${emoji.success} GOOD: border: "1px solid var(--border-color)" (direct variable)
|
|
1402
|
+
*
|
|
1403
|
+
* Known problematic variables:
|
|
1404
|
+
* - --glass-surface-border (chains to --border-color)
|
|
1405
|
+
* - --glass-border-* (may not resolve in all contexts)
|
|
1406
|
+
*/
|
|
1407
|
+
async checkChainedCssVariables() {
|
|
1408
|
+
const startTime = Date.now();
|
|
1409
|
+
const issues = [];
|
|
1410
|
+
const allFiles = await this.getAllFiles();
|
|
1411
|
+
// Variables that chain through intermediate variables and may not resolve
|
|
1412
|
+
const PROBLEMATIC_VARIABLES = [
|
|
1413
|
+
{
|
|
1414
|
+
pattern: /var\(--glass-surface-border\)/,
|
|
1415
|
+
name: "--glass-surface-border",
|
|
1416
|
+
replacement: "--border-color",
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
pattern: /var\(--glass-border-light\)/,
|
|
1420
|
+
name: "--glass-border-light",
|
|
1421
|
+
replacement: "--border-color",
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
pattern: /var\(--glass-border-medium\)/,
|
|
1425
|
+
name: "--glass-border-medium",
|
|
1426
|
+
replacement: "--border-color",
|
|
1427
|
+
},
|
|
1428
|
+
{
|
|
1429
|
+
pattern: /var\(--glass-border-strong\)/,
|
|
1430
|
+
name: "--glass-border-strong",
|
|
1431
|
+
replacement: "--border-color",
|
|
1432
|
+
},
|
|
1433
|
+
];
|
|
1434
|
+
for (const file of allFiles) {
|
|
1435
|
+
// Skip CSS files - these define the variables
|
|
1436
|
+
if (file.endsWith(".css"))
|
|
1437
|
+
continue;
|
|
1438
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1439
|
+
const lines = content.split("\n");
|
|
1440
|
+
lines.forEach((line, index) => {
|
|
1441
|
+
if (line.trim().startsWith("//"))
|
|
1442
|
+
return;
|
|
1443
|
+
// Check for problematic variables in inline styles
|
|
1444
|
+
for (const variable of PROBLEMATIC_VARIABLES) {
|
|
1445
|
+
if (variable.pattern.test(line)) {
|
|
1446
|
+
// Only flag if this is in a style prop (inline style)
|
|
1447
|
+
const isInlineStyle = /style=\{|style:\s*\{/.test(line) ||
|
|
1448
|
+
(index > 0 && /style=\{/.test(lines.slice(Math.max(0, index - 5), index).join("\n")));
|
|
1449
|
+
if (isInlineStyle) {
|
|
1450
|
+
issues.push({
|
|
1451
|
+
file,
|
|
1452
|
+
line: index + 1,
|
|
1453
|
+
type: "chained-css-variable",
|
|
1454
|
+
severity: "error",
|
|
1455
|
+
message: `Chained CSS variable ${variable.name} may not resolve in inline styles`,
|
|
1456
|
+
suggestion: `Use var(${variable.replacement}) directly instead`,
|
|
1457
|
+
snippet: line.trim().substring(0, 100),
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1465
|
+
return {
|
|
1466
|
+
name: "Chained CSS Variable Check",
|
|
1467
|
+
passed: !hasErrors,
|
|
1468
|
+
blocking: true,
|
|
1469
|
+
issues,
|
|
1470
|
+
duration: Date.now() - startTime,
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Duplicate className Props Check (BLOCKING)
|
|
1475
|
+
*
|
|
1476
|
+
* Detects JSX elements with multiple className props, where the second one
|
|
1477
|
+
* overwrites the first, causing styles to be lost.
|
|
1478
|
+
*
|
|
1479
|
+
* ${emoji.error} BAD: <Box className="p-4" onClick={...} className="hover:shadow-lg" />
|
|
1480
|
+
* ${emoji.success} GOOD: <Box className="p-4 hover:shadow-lg" onClick={...} />
|
|
1481
|
+
* ${emoji.success} GOOD: <Box className={`p-4 ${onClick ? "hover:shadow-lg" : ""}`} />
|
|
1482
|
+
*/
|
|
1483
|
+
async checkDuplicateClassNameProps() {
|
|
1484
|
+
const startTime = Date.now();
|
|
1485
|
+
const issues = [];
|
|
1486
|
+
const allFiles = await this.getAllFiles();
|
|
1487
|
+
for (const file of allFiles) {
|
|
1488
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1489
|
+
const lines = content.split("\n");
|
|
1490
|
+
// Track open JSX elements across lines - only track the opening tag, not children
|
|
1491
|
+
let inOpeningTag = false;
|
|
1492
|
+
let tagStartLine = 0;
|
|
1493
|
+
let classNameCount = 0;
|
|
1494
|
+
let tagContent = "";
|
|
1495
|
+
lines.forEach((line, index) => {
|
|
1496
|
+
if (line.trim().startsWith("//"))
|
|
1497
|
+
return;
|
|
1498
|
+
// Simple single-line check - only count className= that are direct props on same element
|
|
1499
|
+
// Look for pattern: <Element className="..." ... className="...">
|
|
1500
|
+
const singleLineMatch = line.match(/<[A-Za-z][A-Za-z0-9]*[^>]*>/g);
|
|
1501
|
+
if (singleLineMatch) {
|
|
1502
|
+
for (const tag of singleLineMatch) {
|
|
1503
|
+
const classNameMatches = tag.match(/\bclassName=/g);
|
|
1504
|
+
if (classNameMatches && classNameMatches.length > 1) {
|
|
1505
|
+
issues.push({
|
|
1506
|
+
file,
|
|
1507
|
+
line: index + 1,
|
|
1508
|
+
type: "duplicate-classname-prop",
|
|
1509
|
+
severity: "error",
|
|
1510
|
+
message: "Multiple className props on same element - second one overwrites first",
|
|
1511
|
+
suggestion: "Merge className values into a single prop using template literals or cn() utility",
|
|
1512
|
+
snippet: line.trim().substring(0, 100),
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
// Multi-line JSX opening tag tracking
|
|
1518
|
+
// Start tracking when we see <Component (uppercase) without closing >
|
|
1519
|
+
if (!inOpeningTag &&
|
|
1520
|
+
/<[A-Z][a-zA-Z0-9]*\s/.test(line) &&
|
|
1521
|
+
!/>/.test(line.split(/<[A-Z][a-zA-Z0-9]*/)[1] || "")) {
|
|
1522
|
+
inOpeningTag = true;
|
|
1523
|
+
tagStartLine = index;
|
|
1524
|
+
tagContent = line;
|
|
1525
|
+
classNameCount = (line.match(/\bclassName=/g) || []).length;
|
|
1526
|
+
}
|
|
1527
|
+
else if (inOpeningTag) {
|
|
1528
|
+
tagContent += "\n" + line;
|
|
1529
|
+
// Only count className= if this line is still part of the opening tag props
|
|
1530
|
+
// (before the first > that closes the opening tag)
|
|
1531
|
+
const beforeClose = line.split(">")[0];
|
|
1532
|
+
// Don't count if this line starts a nested element
|
|
1533
|
+
if (!/<[A-Za-z]/.test(beforeClose)) {
|
|
1534
|
+
classNameCount += (beforeClose.match(/\bclassName=/g) || []).length;
|
|
1535
|
+
}
|
|
1536
|
+
// Check if opening tag closes on this line
|
|
1537
|
+
if (/>/.test(line)) {
|
|
1538
|
+
// Opening tag closed
|
|
1539
|
+
if (classNameCount > 1) {
|
|
1540
|
+
issues.push({
|
|
1541
|
+
file,
|
|
1542
|
+
line: tagStartLine + 1,
|
|
1543
|
+
type: "duplicate-classname-prop",
|
|
1544
|
+
severity: "error",
|
|
1545
|
+
message: "Multiple className props on same element - second one overwrites first",
|
|
1546
|
+
suggestion: "Merge className values into a single prop using template literals or cn() utility",
|
|
1547
|
+
snippet: lines[tagStartLine].trim().substring(0, 100),
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
inOpeningTag = false;
|
|
1551
|
+
classNameCount = 0;
|
|
1552
|
+
tagContent = "";
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1558
|
+
return {
|
|
1559
|
+
name: "Duplicate className Props Check",
|
|
1560
|
+
passed: !hasErrors,
|
|
1561
|
+
blocking: true,
|
|
1562
|
+
issues,
|
|
1563
|
+
duration: Date.now() - startTime,
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* Hardcoded Colors
|
|
1568
|
+
* Detects internal colors that should use CSS variables (allows platform brand colors)
|
|
1569
|
+
*/
|
|
1570
|
+
async checkHardcodedColors() {
|
|
1571
|
+
const startTime = Date.now();
|
|
1572
|
+
const issues = [];
|
|
1573
|
+
const allFiles = await this.getAllFiles();
|
|
1574
|
+
for (const file of allFiles) {
|
|
1575
|
+
// Skip platform icon files
|
|
1576
|
+
if (file.includes("PlatformIcon"))
|
|
1577
|
+
continue;
|
|
1578
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1579
|
+
const lines = content.split("\n");
|
|
1580
|
+
lines.forEach((line, index) => {
|
|
1581
|
+
if (line.trim().startsWith("//"))
|
|
1582
|
+
return;
|
|
1583
|
+
if (line.includes("var(--color-"))
|
|
1584
|
+
return; // Already using tokens
|
|
1585
|
+
// Check for hex colors
|
|
1586
|
+
const hexMatches = line.matchAll(/#([0-9a-fA-F]{3,6})\b/g);
|
|
1587
|
+
for (const match of hexMatches) {
|
|
1588
|
+
const color = `#${match[1]}`.toLowerCase();
|
|
1589
|
+
// Skip platform brand colors (OK to hardcode)
|
|
1590
|
+
if (PLATFORM_BRAND_COLORS.includes(color))
|
|
1591
|
+
continue;
|
|
1592
|
+
// Skip common values
|
|
1593
|
+
if (["#fff", "#ffffff", "#000", "#000000"].includes(color))
|
|
1594
|
+
continue;
|
|
1595
|
+
// Skip primary brand color used in metadata (themeColor, msapplication-TileColor)
|
|
1596
|
+
// These cannot use CSS variables as browsers parse them before stylesheets load
|
|
1597
|
+
if (["#1e63f1"].includes(color))
|
|
1598
|
+
continue;
|
|
1599
|
+
// Check context - is this in a styling property?
|
|
1600
|
+
if (/(color|background|bg|border|fill|stroke)/.test(line.toLowerCase())) {
|
|
1601
|
+
issues.push({
|
|
1602
|
+
file,
|
|
1603
|
+
line: index + 1,
|
|
1604
|
+
type: "hardcoded-color",
|
|
1605
|
+
severity: "info",
|
|
1606
|
+
message: `Hardcoded color: ${color}`,
|
|
1607
|
+
suggestion: "Use var(--color-{name}) or var(--text-{name}) for consistency",
|
|
1608
|
+
snippet: line.trim().substring(0, 80),
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
return {
|
|
1615
|
+
name: "Hardcoded Colors",
|
|
1616
|
+
passed: true,
|
|
1617
|
+
blocking: true,
|
|
1618
|
+
issues,
|
|
1619
|
+
duration: Date.now() - startTime,
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Autocomplete UX Guardrails (BLOCKING)
|
|
1624
|
+
*
|
|
1625
|
+
* Enforce consistent Autocomplete UX across the site:
|
|
1626
|
+
* - Disallow `floatingLabel={false}` (we use label-in-border globally)
|
|
1627
|
+
* - Disallow `openOnFocus={true}` (can create unexpected large dropdown panels)
|
|
1628
|
+
* - Disallow manual <label> directly above an <Autocomplete>
|
|
1629
|
+
*/
|
|
1630
|
+
async checkAutocompleteUX() {
|
|
1631
|
+
const startTime = Date.now();
|
|
1632
|
+
const issues = [];
|
|
1633
|
+
const files = await getAppComponentsTsxFiles();
|
|
1634
|
+
for (const file of files) {
|
|
1635
|
+
const normalizedFile = file.replace(/\\/g, "/");
|
|
1636
|
+
// Ignore UI primitive implementation itself
|
|
1637
|
+
if (normalizedFile.startsWith("components/ui/"))
|
|
1638
|
+
continue;
|
|
1639
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1640
|
+
if (!content.includes("Autocomplete") && !content.includes("<label"))
|
|
1641
|
+
continue;
|
|
1642
|
+
const lines = content.split("\n");
|
|
1643
|
+
// 1) Disallow opt-out of floating labels
|
|
1644
|
+
lines.forEach((line, index) => {
|
|
1645
|
+
if (/floatingLabel\s*=\s*\{?\s*false\s*\}?/.test(line)) {
|
|
1646
|
+
issues.push({
|
|
1647
|
+
file,
|
|
1648
|
+
line: index + 1,
|
|
1649
|
+
type: "autocomplete-floatinglabel-disabled",
|
|
1650
|
+
severity: "error",
|
|
1651
|
+
message: "Autocomplete floating labels must remain enabled (no floatingLabel={false})",
|
|
1652
|
+
suggestion: "Remove `floatingLabel={false}` and rely on the shared Autocomplete default.",
|
|
1653
|
+
snippet: line.trim().substring(0, 140),
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
if (/openOnFocus\s*=\s*\{?\s*true\s*\}?/.test(line)) {
|
|
1657
|
+
issues.push({
|
|
1658
|
+
file,
|
|
1659
|
+
line: index + 1,
|
|
1660
|
+
type: "autocomplete-openonfocus-enabled",
|
|
1661
|
+
severity: "error",
|
|
1662
|
+
message: "Autocomplete should not auto-open on focus (openOnFocus={true} is disallowed)",
|
|
1663
|
+
suggestion: "Remove `openOnFocus` (default is false) and rely on click-to-open behavior.",
|
|
1664
|
+
snippet: line.trim().substring(0, 140),
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
});
|
|
1668
|
+
// 2) Disallow manual <label> directly above <Autocomplete>
|
|
1669
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1670
|
+
const line = lines[i];
|
|
1671
|
+
if (!/<label\b/i.test(line))
|
|
1672
|
+
continue;
|
|
1673
|
+
if (line.trim().startsWith("//"))
|
|
1674
|
+
continue;
|
|
1675
|
+
// Look ahead for the next meaningful line
|
|
1676
|
+
for (let j = i + 1; j < Math.min(i + 6, lines.length); j++) {
|
|
1677
|
+
const next = lines[j].trim();
|
|
1678
|
+
if (!next || next.startsWith("//"))
|
|
1679
|
+
continue;
|
|
1680
|
+
if (next.startsWith("{/*") || next.startsWith("*/}"))
|
|
1681
|
+
continue;
|
|
1682
|
+
if (/<Autocomplete\b/.test(next)) {
|
|
1683
|
+
issues.push({
|
|
1684
|
+
file,
|
|
1685
|
+
line: i + 1,
|
|
1686
|
+
type: "autocomplete-manual-label",
|
|
1687
|
+
severity: "error",
|
|
1688
|
+
message: "Manual <label> directly above <Autocomplete> is not allowed (use Autocomplete label prop only)",
|
|
1689
|
+
suggestion: "Remove the manual <label> and pass its text via the Autocomplete `label` prop.",
|
|
1690
|
+
snippet: line.trim().substring(0, 140),
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
break;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1698
|
+
return {
|
|
1699
|
+
name: "Autocomplete UX Guardrails",
|
|
1700
|
+
passed: !hasErrors,
|
|
1701
|
+
blocking: true,
|
|
1702
|
+
issues,
|
|
1703
|
+
duration: Date.now() - startTime,
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Autocomplete Implementation Guardrails (BLOCKING)
|
|
1708
|
+
*
|
|
1709
|
+
* Ensure the shared Autocomplete keeps the runtime protections that prevent
|
|
1710
|
+
* conditional-mount click-through from opening the dropdown and visually
|
|
1711
|
+
* "whiting out" large parts of the page.
|
|
1712
|
+
*/
|
|
1713
|
+
async checkAutocompleteImplementation() {
|
|
1714
|
+
const startTime = Date.now();
|
|
1715
|
+
const issues = [];
|
|
1716
|
+
const file = "components/ui/Autocomplete.tsx";
|
|
1717
|
+
try {
|
|
1718
|
+
const content = fs.readFileSync(file, "utf8");
|
|
1719
|
+
const requiredSnippets = [
|
|
1720
|
+
{ needle: /openOnFocus\s*=\s*false/, label: "default openOnFocus=false" },
|
|
1721
|
+
{
|
|
1722
|
+
needle: /onPointerDown=\{\(\)\s*=>\s*\{\s*\n?\s*lastPointerDownAtRef\.current\s*=\s*Date\.now\(\)/m,
|
|
1723
|
+
label: "pointerdown gating for click-to-open",
|
|
1724
|
+
},
|
|
1725
|
+
{
|
|
1726
|
+
needle: /ignoreClicksUntilRef\s*=\s*useRef<\s*number\s*>\(\s*Date\.now\(\)\s*\+\s*\d+/m,
|
|
1727
|
+
label: "initial click-through ignore window",
|
|
1728
|
+
},
|
|
1729
|
+
];
|
|
1730
|
+
for (const req of requiredSnippets) {
|
|
1731
|
+
if (!req.needle.test(content)) {
|
|
1732
|
+
issues.push({
|
|
1733
|
+
file,
|
|
1734
|
+
line: 1,
|
|
1735
|
+
type: "autocomplete-implementation-guard",
|
|
1736
|
+
severity: "error",
|
|
1737
|
+
message: `Shared Autocomplete missing required guardrail: ${req.label}`,
|
|
1738
|
+
suggestion: "Do not remove the click-through protections; they prevent the shipping page from being visually obscured when toggling features.",
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
catch {
|
|
1744
|
+
issues.push({
|
|
1745
|
+
file,
|
|
1746
|
+
line: 1,
|
|
1747
|
+
type: "autocomplete-implementation-guard",
|
|
1748
|
+
severity: "error",
|
|
1749
|
+
message: "Could not read shared Autocomplete implementation to validate click-through protections.",
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
1753
|
+
return {
|
|
1754
|
+
name: "Autocomplete Implementation Guardrails",
|
|
1755
|
+
passed: !hasErrors,
|
|
1756
|
+
blocking: true,
|
|
1757
|
+
issues,
|
|
1758
|
+
duration: Date.now() - startTime,
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
async runAll() {
|
|
1762
|
+
const startTime = Date.now();
|
|
1763
|
+
console.log("\nš§© CONSOLIDATED UI COMPONENTS PREFLIGHT");
|
|
1764
|
+
console.log((0, console_chars_1.createDivider)(80, "heavy"));
|
|
1765
|
+
const checks = [];
|
|
1766
|
+
checks.push(await this.checkButtons());
|
|
1767
|
+
checks.push(await this.checkLinkButtons());
|
|
1768
|
+
checks.push(await this.checkClickableNonButtons());
|
|
1769
|
+
checks.push(await this.checkIconButtonAccessibility());
|
|
1770
|
+
checks.push(await this.checkBadges());
|
|
1771
|
+
checks.push(await this.checkIcons());
|
|
1772
|
+
checks.push(await this.checkBorderRadius());
|
|
1773
|
+
checks.push(await this.checkShadows());
|
|
1774
|
+
checks.push(await this.checkChips());
|
|
1775
|
+
checks.push(await this.checkPlatformIcons());
|
|
1776
|
+
checks.push(await this.checkNumericSpacing());
|
|
1777
|
+
checks.push(await this.checkHardcodedColors());
|
|
1778
|
+
checks.push(await this.checkAutocompleteUX());
|
|
1779
|
+
checks.push(await this.checkAutocompleteImplementation());
|
|
1780
|
+
checks.push(await this.checkCardOutlines());
|
|
1781
|
+
checks.push(await this.checkCardBackground());
|
|
1782
|
+
checks.push(await this.checkCardMissingBackground());
|
|
1783
|
+
checks.push(await this.checkMetricGridGlassVariant());
|
|
1784
|
+
checks.push(await this.checkChainedCssVariables());
|
|
1785
|
+
checks.push(await this.checkDuplicateClassNameProps());
|
|
1786
|
+
checks.push(await this.checkUnifiedDialogPadding());
|
|
1787
|
+
checks.push(await this.checkDeprecatedComponents());
|
|
1788
|
+
checks.push(await this.checkCustomStatDisplays());
|
|
1789
|
+
const totalDuration = Date.now() - startTime;
|
|
1790
|
+
const allIssues = checks.flatMap((c) => c.issues);
|
|
1791
|
+
const summary = {
|
|
1792
|
+
total: checks.length,
|
|
1793
|
+
passed: checks.filter((c) => c.passed).length,
|
|
1794
|
+
failed: checks.filter((c) => !c.passed).length,
|
|
1795
|
+
errors: allIssues.filter((i) => i.severity === "error").length,
|
|
1796
|
+
warnings: allIssues.filter((i) => i.severity === "warning").length,
|
|
1797
|
+
info: allIssues.filter((i) => i.severity === "info").length,
|
|
1798
|
+
};
|
|
1799
|
+
this.printResults(checks, totalDuration, summary);
|
|
1800
|
+
return {
|
|
1801
|
+
module: "ui-components",
|
|
1802
|
+
passed: true,
|
|
1803
|
+
totalDuration,
|
|
1804
|
+
checks,
|
|
1805
|
+
summary,
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
printResults(checks, totalDuration, summary) {
|
|
1809
|
+
console.log("\n" + (0, console_chars_1.createDivider)(80, "heavy"));
|
|
1810
|
+
console.log(`${console_chars_1.emoji.chart} UI COMPONENTS RESULTS`);
|
|
1811
|
+
console.log((0, console_chars_1.createDivider)(80, "heavy"));
|
|
1812
|
+
checks.forEach((check) => {
|
|
1813
|
+
const icon = check.issues.length === 0 ? `${console_chars_1.emoji.success}` : `${console_chars_1.emoji.warning}`;
|
|
1814
|
+
const count = check.issues.length > 0 ? ` (${check.issues.length} issues)` : "";
|
|
1815
|
+
console.log(`${icon} ${check.name.padEnd(30)} ${(check.duration / 1000).toFixed(1)}s${count}`);
|
|
1816
|
+
});
|
|
1817
|
+
console.log((0, console_chars_1.createDivider)(80, "heavy"));
|
|
1818
|
+
console.log(`${console_chars_1.emoji.warning} Warnings: ${summary.warnings} | ${console_chars_1.emoji.info} Info: ${summary.info} | ${console_chars_1.emoji.clock} Time: ${(totalDuration / 1000).toFixed(1)}s`);
|
|
1819
|
+
// Print detailed issues if verbose mode or if there are warnings/errors
|
|
1820
|
+
const allIssues = checks.flatMap((c) => c.issues);
|
|
1821
|
+
const warningsAndErrors = allIssues.filter((i) => i.severity === "warning" || i.severity === "error");
|
|
1822
|
+
if (this.verbose || warningsAndErrors.length > 0) {
|
|
1823
|
+
console.log(`\n${console_chars_1.emoji.clipboard} DETAILED ISSUES:`);
|
|
1824
|
+
console.log((0, console_chars_1.createDivider)(80, "heavy"));
|
|
1825
|
+
checks.forEach((check) => {
|
|
1826
|
+
if (check.issues.length > 0) {
|
|
1827
|
+
console.log(`\n${console_chars_1.emoji.search} ${check.name}:`);
|
|
1828
|
+
check.issues.forEach((issue) => {
|
|
1829
|
+
const icon = issue.severity === "error"
|
|
1830
|
+
? `${console_chars_1.emoji.error}`
|
|
1831
|
+
: issue.severity === "warning"
|
|
1832
|
+
? `${console_chars_1.emoji.warning}`
|
|
1833
|
+
: `${console_chars_1.emoji.info}`;
|
|
1834
|
+
const relPath = issue.file.replace(/\\/g, "/");
|
|
1835
|
+
console.log(` ${icon} ${relPath}:${issue.line}`);
|
|
1836
|
+
console.log(` ${issue.message}`);
|
|
1837
|
+
if (issue.suggestion) {
|
|
1838
|
+
console.log(` ${console_chars_1.emoji.hint} ${issue.suggestion}`);
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
console.log(`\n${console_chars_1.emoji.success} UI COMPONENTS PASSED (non-blocking warnings)`);
|
|
1845
|
+
}
|
|
1846
|
+
shouldFailInStrictMode(checks) {
|
|
1847
|
+
if (!this.strictUniformity)
|
|
1848
|
+
return false;
|
|
1849
|
+
return checks.some((c) => c.issues.some((i) => i.severity === "warning" || i.severity === "error"));
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
exports.UIComponentsPreflightModule = UIComponentsPreflightModule;
|
|
1853
|
+
async function main() {
|
|
1854
|
+
appExclusions = await (0, exclusions_1.getExclusions)(id);
|
|
1855
|
+
const reporter = (0, universal_progress_reporter_1.createUniversalProgressReporter)(path.basename(__filename, ".ts"));
|
|
1856
|
+
const args = process.argv.slice(2);
|
|
1857
|
+
const mode = args[0] || "all";
|
|
1858
|
+
const verbose = args.includes("--verbose") || args.includes("-v");
|
|
1859
|
+
const parallel = args.includes("--parallel") || args.includes("-p");
|
|
1860
|
+
const module = new UIComponentsPreflightModule({ verbose, parallel });
|
|
1861
|
+
const runSingle = async (name, fn) => {
|
|
1862
|
+
const check = await fn();
|
|
1863
|
+
// Ensure subset modes still print a useful report
|
|
1864
|
+
const summary = {
|
|
1865
|
+
total: 1,
|
|
1866
|
+
passed: 1,
|
|
1867
|
+
failed: 0,
|
|
1868
|
+
errors: check.issues.filter((i) => i.severity === "error").length,
|
|
1869
|
+
warnings: check.issues.filter((i) => i.severity === "warning").length,
|
|
1870
|
+
info: check.issues.filter((i) => i.severity === "info").length,
|
|
1871
|
+
};
|
|
1872
|
+
module.printResults([check], check.duration, summary);
|
|
1873
|
+
return {
|
|
1874
|
+
module: `ui-components:${name}`,
|
|
1875
|
+
passed: true,
|
|
1876
|
+
totalDuration: check.duration,
|
|
1877
|
+
checks: [check],
|
|
1878
|
+
summary: {
|
|
1879
|
+
total: 1,
|
|
1880
|
+
passed: 1,
|
|
1881
|
+
failed: 0,
|
|
1882
|
+
errors: 0,
|
|
1883
|
+
warnings: check.issues.filter((i) => i.severity === "warning").length,
|
|
1884
|
+
info: check.issues.filter((i) => i.severity === "info").length,
|
|
1885
|
+
},
|
|
1886
|
+
};
|
|
1887
|
+
};
|
|
1888
|
+
let result;
|
|
1889
|
+
switch (mode) {
|
|
1890
|
+
case "buttons":
|
|
1891
|
+
result = await runSingle("buttons", () => module.checkButtons());
|
|
1892
|
+
break;
|
|
1893
|
+
case "links":
|
|
1894
|
+
result = await runSingle("links", () => module.checkLinkButtons());
|
|
1895
|
+
break;
|
|
1896
|
+
case "clickables":
|
|
1897
|
+
result = await runSingle("clickables", () => module.checkClickableNonButtons());
|
|
1898
|
+
break;
|
|
1899
|
+
case "iconbuttons":
|
|
1900
|
+
result = await runSingle("iconbuttons", () => module.checkIconButtonAccessibility());
|
|
1901
|
+
break;
|
|
1902
|
+
case "uniformity":
|
|
1903
|
+
// Uniformity mode: highest-signal checks that prevent UI drift.
|
|
1904
|
+
// Kept warning-only by default; can be made strict with PREFLIGHT_STRICT_UNIFORMITY=1.
|
|
1905
|
+
console.log("\nš§ UI UNIFORMITY (subset)");
|
|
1906
|
+
console.log((0, console_chars_1.createDivider)(80, "heavy"));
|
|
1907
|
+
const uniformityChecksPromises = [
|
|
1908
|
+
module.checkButtons(),
|
|
1909
|
+
module.checkLinkButtons(),
|
|
1910
|
+
module.checkClickableNonButtons(),
|
|
1911
|
+
module.checkIconButtonAccessibility(),
|
|
1912
|
+
module.checkUnifiedDialogPadding(),
|
|
1913
|
+
];
|
|
1914
|
+
const uniformityChecks = await Promise.all(uniformityChecksPromises);
|
|
1915
|
+
module.printResults(uniformityChecks, uniformityChecks.reduce((sum, c) => sum + c.duration, 0), {
|
|
1916
|
+
total: uniformityChecks.length,
|
|
1917
|
+
passed: uniformityChecks.length,
|
|
1918
|
+
failed: 0,
|
|
1919
|
+
errors: uniformityChecks.flatMap((c) => c.issues).filter((i) => i.severity === "error")
|
|
1920
|
+
.length,
|
|
1921
|
+
warnings: uniformityChecks
|
|
1922
|
+
.flatMap((c) => c.issues)
|
|
1923
|
+
.filter((i) => i.severity === "warning").length,
|
|
1924
|
+
info: uniformityChecks.flatMap((c) => c.issues).filter((i) => i.severity === "info")
|
|
1925
|
+
.length,
|
|
1926
|
+
});
|
|
1927
|
+
result = {
|
|
1928
|
+
module: "ui-components:uniformity",
|
|
1929
|
+
passed: true,
|
|
1930
|
+
totalDuration: uniformityChecks.reduce((sum, c) => sum + c.duration, 0),
|
|
1931
|
+
checks: uniformityChecks,
|
|
1932
|
+
summary: {
|
|
1933
|
+
total: uniformityChecks.length,
|
|
1934
|
+
passed: uniformityChecks.length,
|
|
1935
|
+
failed: 0,
|
|
1936
|
+
errors: 0,
|
|
1937
|
+
warnings: uniformityChecks
|
|
1938
|
+
.flatMap((c) => c.issues)
|
|
1939
|
+
.filter((i) => i.severity === "warning").length,
|
|
1940
|
+
info: uniformityChecks.flatMap((c) => c.issues).filter((i) => i.severity === "info")
|
|
1941
|
+
.length,
|
|
1942
|
+
},
|
|
1943
|
+
};
|
|
1944
|
+
break;
|
|
1945
|
+
case "badges":
|
|
1946
|
+
result = await runSingle("badges", () => module.checkBadges());
|
|
1947
|
+
break;
|
|
1948
|
+
case "icons":
|
|
1949
|
+
result = await runSingle("icons", () => module.checkIcons());
|
|
1950
|
+
break;
|
|
1951
|
+
case "radius":
|
|
1952
|
+
result = await runSingle("radius", () => module.checkBorderRadius());
|
|
1953
|
+
break;
|
|
1954
|
+
case "shadows":
|
|
1955
|
+
result = await runSingle("shadows", () => module.checkShadows());
|
|
1956
|
+
break;
|
|
1957
|
+
case "chips":
|
|
1958
|
+
result = await runSingle("chips", () => module.checkChips());
|
|
1959
|
+
break;
|
|
1960
|
+
case "platforms":
|
|
1961
|
+
result = await runSingle("platforms", () => module.checkPlatformIcons());
|
|
1962
|
+
break;
|
|
1963
|
+
case "spacing":
|
|
1964
|
+
result = await runSingle("spacing", () => module.checkNumericSpacing());
|
|
1965
|
+
break;
|
|
1966
|
+
case "colors":
|
|
1967
|
+
result = await runSingle("colors", () => module.checkHardcodedColors());
|
|
1968
|
+
break;
|
|
1969
|
+
case "cards":
|
|
1970
|
+
result = await runSingle("cards", () => module.checkCardOutlines());
|
|
1971
|
+
break;
|
|
1972
|
+
case "card-backgrounds":
|
|
1973
|
+
result = await runSingle("card-backgrounds", () => module.checkCardBackground());
|
|
1974
|
+
break;
|
|
1975
|
+
case "card-missing-bg":
|
|
1976
|
+
case "missing-backgrounds":
|
|
1977
|
+
result = await runSingle("card-missing-backgrounds", () => module.checkCardMissingBackground());
|
|
1978
|
+
break;
|
|
1979
|
+
case "metric-grid":
|
|
1980
|
+
case "glass":
|
|
1981
|
+
result = await runSingle("metric-grid-glass", () => module.checkMetricGridGlassVariant());
|
|
1982
|
+
break;
|
|
1983
|
+
case "css-variables":
|
|
1984
|
+
case "chained-vars":
|
|
1985
|
+
result = await runSingle("chained-css-variables", () => module.checkChainedCssVariables());
|
|
1986
|
+
break;
|
|
1987
|
+
case "duplicate-classname":
|
|
1988
|
+
case "classname":
|
|
1989
|
+
result = await runSingle("duplicate-classname", () => module.checkDuplicateClassNameProps());
|
|
1990
|
+
break;
|
|
1991
|
+
case "deprecated":
|
|
1992
|
+
result = await runSingle("deprecated", () => module.checkDeprecatedComponents());
|
|
1993
|
+
break;
|
|
1994
|
+
case "stat-displays":
|
|
1995
|
+
case "stats":
|
|
1996
|
+
result = await runSingle("stat-displays", () => module.checkCustomStatDisplays());
|
|
1997
|
+
break;
|
|
1998
|
+
default:
|
|
1999
|
+
result = await module.runAll();
|
|
2000
|
+
break;
|
|
2001
|
+
}
|
|
2002
|
+
const strictFail = module.shouldFailInStrictMode(result.checks);
|
|
2003
|
+
process.exit(strictFail ? 1 : 0);
|
|
2004
|
+
}
|
|
2005
|
+
if (require.main === module) {
|
|
2006
|
+
main();
|
|
2007
|
+
}
|
|
2008
|
+
//# sourceMappingURL=ui-components.js.map
|