@musashishao/agent-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +487 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +76 -0
- package/.agent/ARCHITECTURE.md +225 -0
- package/.agent/CONTEXT.md +229 -0
- package/.agent/FEATURE_ROADMAP.md +435 -0
- package/.agent/PROMPT_TEMPLATES.md +261 -0
- package/.agent/agents/backend-specialist.md +263 -0
- package/.agent/agents/database-architect.md +226 -0
- package/.agent/agents/debugger.md +225 -0
- package/.agent/agents/devops-engineer.md +242 -0
- package/.agent/agents/documentation-writer.md +104 -0
- package/.agent/agents/explorer-agent.md +73 -0
- package/.agent/agents/frontend-specialist.md +556 -0
- package/.agent/agents/game-developer.md +162 -0
- package/.agent/agents/mobile-developer.md +377 -0
- package/.agent/agents/orchestrator.md +416 -0
- package/.agent/agents/penetration-tester.md +188 -0
- package/.agent/agents/performance-optimizer.md +187 -0
- package/.agent/agents/project-planner.md +403 -0
- package/.agent/agents/security-auditor.md +170 -0
- package/.agent/agents/seo-specialist.md +111 -0
- package/.agent/agents/test-engineer.md +158 -0
- package/.agent/rules/GEMINI.md +251 -0
- package/.agent/skills/api-patterns/SKILL.md +81 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +82 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +100 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +106 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +101 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +93 -0
- package/.agent/skills/architecture/SKILL.md +55 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/bash-linux/SKILL.md +199 -0
- package/.agent/skills/behavioral-modes/SKILL.md +242 -0
- package/.agent/skills/brainstorming/SKILL.md +163 -0
- package/.agent/skills/brainstorming/dynamic-questioning.md +350 -0
- package/.agent/skills/clean-code/SKILL.md +201 -0
- package/.agent/skills/code-review-checklist/SKILL.md +109 -0
- package/.agent/skills/database-design/SKILL.md +52 -0
- package/.agent/skills/database-design/database-selection.md +43 -0
- package/.agent/skills/database-design/indexing.md +39 -0
- package/.agent/skills/database-design/migrations.md +48 -0
- package/.agent/skills/database-design/optimization.md +36 -0
- package/.agent/skills/database-design/orm-selection.md +30 -0
- package/.agent/skills/database-design/schema-design.md +56 -0
- package/.agent/skills/database-design/scripts/schema_validator.py +172 -0
- package/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/docker-expert/SKILL.md +409 -0
- package/.agent/skills/documentation-templates/SKILL.md +194 -0
- package/.agent/skills/frontend-design/SKILL.md +396 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +311 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +541 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/game-development/2d-games/SKILL.md +119 -0
- package/.agent/skills/game-development/3d-games/SKILL.md +135 -0
- package/.agent/skills/game-development/SKILL.md +167 -0
- package/.agent/skills/game-development/game-art/SKILL.md +185 -0
- package/.agent/skills/game-development/game-audio/SKILL.md +190 -0
- package/.agent/skills/game-development/game-design/SKILL.md +129 -0
- package/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
- package/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
- package/.agent/skills/game-development/pc-games/SKILL.md +144 -0
- package/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
- package/.agent/skills/game-development/web-games/SKILL.md +150 -0
- package/.agent/skills/geo-fundamentals/SKILL.md +156 -0
- package/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/lint-and-validate/SKILL.md +45 -0
- package/.agent/skills/lint-and-validate/scripts/lint_runner.py +172 -0
- package/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/mobile-design/SKILL.md +394 -0
- package/.agent/skills/mobile-design/decision-trees.md +516 -0
- package/.agent/skills/mobile-design/mobile-backend.md +491 -0
- package/.agent/skills/mobile-design/mobile-color-system.md +420 -0
- package/.agent/skills/mobile-design/mobile-debugging.md +122 -0
- package/.agent/skills/mobile-design/mobile-design-thinking.md +357 -0
- package/.agent/skills/mobile-design/mobile-navigation.md +458 -0
- package/.agent/skills/mobile-design/mobile-performance.md +767 -0
- package/.agent/skills/mobile-design/mobile-testing.md +356 -0
- package/.agent/skills/mobile-design/mobile-typography.md +433 -0
- package/.agent/skills/mobile-design/platform-android.md +666 -0
- package/.agent/skills/mobile-design/platform-ios.md +561 -0
- package/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/.agent/skills/mobile-design/touch-psychology.md +537 -0
- package/.agent/skills/nestjs-expert/SKILL.md +552 -0
- package/.agent/skills/nextjs-best-practices/SKILL.md +203 -0
- package/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
- package/.agent/skills/parallel-agents/SKILL.md +175 -0
- package/.agent/skills/performance-profiling/SKILL.md +143 -0
- package/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/.agent/skills/plan-writing/SKILL.md +152 -0
- package/.agent/skills/powershell-windows/SKILL.md +167 -0
- package/.agent/skills/prisma-expert/SKILL.md +355 -0
- package/.agent/skills/python-patterns/SKILL.md +441 -0
- package/.agent/skills/react-patterns/SKILL.md +198 -0
- package/.agent/skills/red-team-tactics/SKILL.md +199 -0
- package/.agent/skills/seo-fundamentals/SKILL.md +129 -0
- package/.agent/skills/seo-fundamentals/scripts/seo_checker.py +219 -0
- package/.agent/skills/server-management/SKILL.md +161 -0
- package/.agent/skills/systematic-debugging/SKILL.md +109 -0
- package/.agent/skills/tailwind-patterns/SKILL.md +269 -0
- package/.agent/skills/tdd-workflow/SKILL.md +149 -0
- package/.agent/skills/testing-patterns/SKILL.md +178 -0
- package/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/.agent/skills/typescript-expert/SKILL.md +429 -0
- package/.agent/skills/typescript-expert/references/tsconfig-strict.json +92 -0
- package/.agent/skills/typescript-expert/references/typescript-cheatsheet.md +383 -0
- package/.agent/skills/typescript-expert/references/utility-types.ts +335 -0
- package/.agent/skills/typescript-expert/scripts/ts_diagnostic.py +203 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +351 -0
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/scripts/core.py +257 -0
- package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +487 -0
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +76 -0
- package/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
- package/.agent/skills/vulnerability-scanner/checklists.md +121 -0
- package/.agent/skills/vulnerability-scanner/scripts/security_scan.py +458 -0
- package/.agent/skills/webapp-testing/SKILL.md +187 -0
- package/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +237 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +80 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +231 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/bin/cli.js +235 -0
- package/index.js +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Mobile UX Audit Script - Full Mobile Design Coverage
|
|
4
|
+
|
|
5
|
+
Analyzes React Native / Flutter code for compliance with:
|
|
6
|
+
|
|
7
|
+
1. TOUCH PSYCHOLOGY (touch-psychology.md):
|
|
8
|
+
- Touch Target Sizes (44pt iOS, 48dp Android, 44px WCAG)
|
|
9
|
+
- Touch Target Spacing (8px minimum gap)
|
|
10
|
+
- Thumb Zone Placement (primary CTAs at bottom)
|
|
11
|
+
- Gesture Alternatives (visible buttons for swipe)
|
|
12
|
+
- Haptic Feedback Patterns
|
|
13
|
+
- Touch Feedback Timing (<50ms)
|
|
14
|
+
- Touch Accessibility (motor impairment support)
|
|
15
|
+
|
|
16
|
+
2. MOBILE PERFORMANCE (mobile-performance.md):
|
|
17
|
+
- ScrollView vs FlatList (CRITICAL)
|
|
18
|
+
- React.memo for List Items
|
|
19
|
+
- useCallback for renderItem
|
|
20
|
+
- Stable keyExtractor (NOT index)
|
|
21
|
+
- useNativeDriver for Animations
|
|
22
|
+
- Memory Leak Prevention (cleanup)
|
|
23
|
+
- Console.log Detection
|
|
24
|
+
- Inline Function Detection
|
|
25
|
+
- Animation Performance (transform/opacity only)
|
|
26
|
+
|
|
27
|
+
3. MOBILE NAVIGATION (mobile-navigation.md):
|
|
28
|
+
- Tab Bar Max Items (5)
|
|
29
|
+
- Tab State Preservation
|
|
30
|
+
- Proper Back Handling
|
|
31
|
+
- Deep Link Support
|
|
32
|
+
- Navigation Structure
|
|
33
|
+
|
|
34
|
+
4. MOBILE TYPOGRAPHY (mobile-typography.md):
|
|
35
|
+
- System Font Usage
|
|
36
|
+
- Dynamic Type Support (iOS)
|
|
37
|
+
- Text Scaling Constraints
|
|
38
|
+
- Mobile Line Height
|
|
39
|
+
- Font Size Limits
|
|
40
|
+
|
|
41
|
+
5. MOBILE COLOR SYSTEM (mobile-color-system.md):
|
|
42
|
+
- Pure Black Avoidance (#000000)
|
|
43
|
+
- OLED Optimization
|
|
44
|
+
- Dark Mode Support
|
|
45
|
+
- Contrast Ratios
|
|
46
|
+
|
|
47
|
+
6. PLATFORM iOS (platform-ios.md):
|
|
48
|
+
- SF Symbols Usage
|
|
49
|
+
- iOS Navigation Patterns
|
|
50
|
+
- iOS Haptic Types
|
|
51
|
+
- iOS-Specific Components
|
|
52
|
+
|
|
53
|
+
7. PLATFORM ANDROID (platform-android.md):
|
|
54
|
+
- Material Icons Usage
|
|
55
|
+
- Android Navigation Patterns
|
|
56
|
+
- Ripple Effects
|
|
57
|
+
- Android-Specific Components
|
|
58
|
+
|
|
59
|
+
8. MOBILE BACKEND (mobile-backend.md):
|
|
60
|
+
- Secure Storage (NOT AsyncStorage)
|
|
61
|
+
- Offline Handling
|
|
62
|
+
- Push Notification Support
|
|
63
|
+
- API Response Caching
|
|
64
|
+
|
|
65
|
+
Total: 50+ mobile-specific checks
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
import sys
|
|
69
|
+
import os
|
|
70
|
+
import re
|
|
71
|
+
import json
|
|
72
|
+
from pathlib import Path
|
|
73
|
+
|
|
74
|
+
class MobileAuditor:
|
|
75
|
+
def __init__(self):
|
|
76
|
+
self.issues = []
|
|
77
|
+
self.warnings = []
|
|
78
|
+
self.passed_count = 0
|
|
79
|
+
self.files_checked = 0
|
|
80
|
+
|
|
81
|
+
def audit_file(self, filepath: str) -> None:
|
|
82
|
+
try:
|
|
83
|
+
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
|
|
84
|
+
content = f.read()
|
|
85
|
+
except:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
self.files_checked += 1
|
|
89
|
+
filename = os.path.basename(filepath)
|
|
90
|
+
|
|
91
|
+
# Detect framework
|
|
92
|
+
is_react_native = bool(re.search(r'react-native|@react-navigation|React\.Native', content))
|
|
93
|
+
is_flutter = bool(re.search(r'import \'package:flutter|MaterialApp|Widget\.build', content))
|
|
94
|
+
|
|
95
|
+
if not (is_react_native or is_flutter):
|
|
96
|
+
return # Skip non-mobile files
|
|
97
|
+
|
|
98
|
+
# --- 1. TOUCH PSYCHOLOGY CHECKS ---
|
|
99
|
+
|
|
100
|
+
# 1.1 Touch Target Size Check
|
|
101
|
+
# Look for small touch targets
|
|
102
|
+
small_sizes = re.findall(r'(?:width|height|size):\s*([0-3]\d)', content)
|
|
103
|
+
for size in small_sizes:
|
|
104
|
+
if int(size) < 44:
|
|
105
|
+
self.issues.append(f"[Touch Target] {filename}: Touch target size {size}px < 44px minimum (iOS: 44pt, Android: 48dp)")
|
|
106
|
+
|
|
107
|
+
# 1.2 Touch Target Spacing Check
|
|
108
|
+
# Look for inadequate spacing between touchable elements
|
|
109
|
+
small_gaps = re.findall(r'(?:margin|gap):\s*([0-7])\s*(?:px|dp)', content)
|
|
110
|
+
for gap in small_gaps:
|
|
111
|
+
if int(gap) < 8:
|
|
112
|
+
self.warnings.append(f"[Touch Spacing] {filename}: Touch target spacing {gap}px < 8px minimum. Accidental taps risk.")
|
|
113
|
+
|
|
114
|
+
# 1.3 Thumb Zone Placement Check
|
|
115
|
+
# Primary CTAs should be at bottom (easy thumb reach)
|
|
116
|
+
primary_buttons = re.findall(r'(?:testID|id):\s*["\'](?:.*(?:primary|cta|submit|confirm)[^"\']*)["\']', content, re.IGNORECASE)
|
|
117
|
+
has_bottom_placement = bool(re.search(r'position:\s*["\']?absolute["\']?|bottom:\s*\d+|style.*bottom|justifyContent:\s*["\']?flex-end', content))
|
|
118
|
+
if primary_buttons and not has_bottom_placement:
|
|
119
|
+
self.warnings.append(f"[Thumb Zone] {filename}: Primary CTA may not be in thumb zone (bottom). Place primary actions at bottom for easy reach.")
|
|
120
|
+
|
|
121
|
+
# 1.4 Gesture Alternatives Check
|
|
122
|
+
# Swipe actions should have visible button alternatives
|
|
123
|
+
has_swipe_gestures = bool(re.search(r'Swipeable|onSwipe|PanGestureHandler|swipe', content))
|
|
124
|
+
has_visible_buttons = bool(re.search(r'Button.*(?:delete|archive|more)|TouchableOpacity|Pressable', content))
|
|
125
|
+
if has_swipe_gestures and not has_visible_buttons:
|
|
126
|
+
self.warnings.append(f"[Gestures] {filename}: Swipe gestures detected without visible button alternatives. Motor impaired users need alternatives.")
|
|
127
|
+
|
|
128
|
+
# 1.5 Haptic Feedback Check
|
|
129
|
+
# Important actions should have haptic feedback
|
|
130
|
+
has_important_actions = bool(re.search(r'(?:onPress|onSubmit|delete|remove|confirm|purchase)', content))
|
|
131
|
+
has_haptics = bool(re.search(r'Haptics|Vibration|react-native-haptic-feedback|FeedbackManager', content))
|
|
132
|
+
if has_important_actions and not has_haptics:
|
|
133
|
+
self.warnings.append(f"[Haptics] {filename}: Important actions without haptic feedback. Consider adding haptic confirmation.")
|
|
134
|
+
|
|
135
|
+
# 1.6 Touch Feedback Timing Check
|
|
136
|
+
# Touch feedback should be immediate (<50ms)
|
|
137
|
+
if is_react_native:
|
|
138
|
+
has_pressable = bool(re.search(r'Pressable|TouchableOpacity', content))
|
|
139
|
+
has_feedback_state = bool(re.search(r'pressed|style.*opacity|underlay', content))
|
|
140
|
+
if has_pressable and not has_feedback_state:
|
|
141
|
+
self.warnings.append(f"[Touch Feedback] {filename}: Pressable without visual feedback state. Add opacity/scale change for tap confirmation.")
|
|
142
|
+
|
|
143
|
+
# --- 2. MOBILE PERFORMANCE CHECKS ---
|
|
144
|
+
|
|
145
|
+
# 2.1 CRITICAL: ScrollView vs FlatList
|
|
146
|
+
has_scrollview = bool(re.search(r'<ScrollView|ScrollView\.', content))
|
|
147
|
+
has_map_in_scrollview = bool(re.search(r'ScrollView.*\.map\(|ScrollView.*\{.*\.map', content))
|
|
148
|
+
if has_scrollview and has_map_in_scrollview:
|
|
149
|
+
self.issues.append(f"[Performance CRITICAL] {filename}: ScrollView with .map() detected. Use FlatList for lists to prevent memory explosion.")
|
|
150
|
+
|
|
151
|
+
# 2.2 React.memo Check
|
|
152
|
+
if is_react_native:
|
|
153
|
+
has_list = bool(re.search(r'FlatList|FlashList|SectionList', content))
|
|
154
|
+
has_react_memo = bool(re.search(r'React\.memo|memo\(', content))
|
|
155
|
+
if has_list and not has_react_memo:
|
|
156
|
+
self.warnings.append(f"[Performance] {filename}: FlatList without React.memo on list items. Items will re-render on every parent update.")
|
|
157
|
+
|
|
158
|
+
# 2.3 useCallback Check
|
|
159
|
+
if is_react_native:
|
|
160
|
+
has_flatlist = bool(re.search(r'FlatList|FlashList', content))
|
|
161
|
+
has_use_callback = bool(re.search(r'useCallback', content))
|
|
162
|
+
if has_flatlist and not has_use_callback:
|
|
163
|
+
self.warnings.append(f"[Performance] {filename}: FlatList renderItem without useCallback. New function created every render.")
|
|
164
|
+
|
|
165
|
+
# 2.4 keyExtractor Check (CRITICAL)
|
|
166
|
+
if is_react_native:
|
|
167
|
+
has_flatlist = bool(re.search(r'FlatList', content))
|
|
168
|
+
has_key_extractor = bool(re.search(r'keyExtractor', content))
|
|
169
|
+
uses_index_key = bool(re.search(r'key=\{.*index.*\}|key:\s*index', content))
|
|
170
|
+
if has_flatlist and not has_key_extractor:
|
|
171
|
+
self.issues.append(f"[Performance CRITICAL] {filename}: FlatList without keyExtractor. Index-based keys cause bugs on reorder/delete.")
|
|
172
|
+
if uses_index_key:
|
|
173
|
+
self.issues.append(f"[Performance CRITICAL] {filename}: Using index as key. This causes bugs when list changes. Use unique ID from data.")
|
|
174
|
+
|
|
175
|
+
# 2.5 useNativeDriver Check
|
|
176
|
+
if is_react_native:
|
|
177
|
+
has_animated = bool(re.search(r'Animated\.', content))
|
|
178
|
+
has_native_driver = bool(re.search(r'useNativeDriver:\s*true', content))
|
|
179
|
+
has_native_driver_false = bool(re.search(r'useNativeDriver:\s*false', content))
|
|
180
|
+
if has_animated and has_native_driver_false:
|
|
181
|
+
self.warnings.append(f"[Performance] {filename}: Animation with useNativeDriver: false. Use true for 60fps (only supports transform/opacity).")
|
|
182
|
+
if has_animated and not has_native_driver:
|
|
183
|
+
self.warnings.append(f"[Performance] {filename}: Animated component without useNativeDriver. Add useNativeDriver: true for 60fps.")
|
|
184
|
+
|
|
185
|
+
# 2.6 Memory Leak Check
|
|
186
|
+
if is_react_native:
|
|
187
|
+
has_effect = bool(re.search(r'useEffect', content))
|
|
188
|
+
has_cleanup = bool(re.search(r'return\s*\(\)\s*=>|return\s+function', content))
|
|
189
|
+
has_subscriptions = bool(re.search(r'addEventListener|subscribe|\.focus\(\)|\.off\(', content))
|
|
190
|
+
if has_effect and has_subscriptions and not has_cleanup:
|
|
191
|
+
self.issues.append(f"[Memory Leak] {filename}: useEffect with subscriptions but no cleanup function. Memory leak on unmount.")
|
|
192
|
+
|
|
193
|
+
# 2.7 Console.log Detection
|
|
194
|
+
console_logs = len(re.findall(r'console\.log|console\.warn|console\.error|console\.debug', content))
|
|
195
|
+
if console_logs > 5:
|
|
196
|
+
self.warnings.append(f"[Performance] {filename}: {console_logs} console.log statements detected. Remove before production (blocks JS thread).")
|
|
197
|
+
|
|
198
|
+
# 2.8 Inline Function Detection
|
|
199
|
+
if is_react_native:
|
|
200
|
+
inline_functions = re.findall(r'(?:onPress|onPressIn|onPressOut|renderItem):\s*\([^)]*\)\s*=>', content)
|
|
201
|
+
if len(inline_functions) > 3:
|
|
202
|
+
self.warnings.append(f"[Performance] {filename}: {len(inline_functions)} inline arrow functions in props. Creates new function every render. Use useCallback.")
|
|
203
|
+
|
|
204
|
+
# 2.9 Animation Properties Check
|
|
205
|
+
# Warn if animating expensive properties
|
|
206
|
+
animating_layout = bool(re.search(r'Animated\.timing.*(?:width|height|margin|padding)', content))
|
|
207
|
+
if animating_layout:
|
|
208
|
+
self.issues.append(f"[Performance] {filename}: Animating layout properties (width/height/margin). Use transform/opacity for 60fps.")
|
|
209
|
+
|
|
210
|
+
# --- 3. MOBILE NAVIGATION CHECKS ---
|
|
211
|
+
|
|
212
|
+
# 3.1 Tab Bar Max Items Check
|
|
213
|
+
tab_bar_items = len(re.findall(r'Tab\.Screen|createBottomTabNavigator|BottomTab', content))
|
|
214
|
+
if tab_bar_items > 5:
|
|
215
|
+
self.warnings.append(f"[Navigation] {filename}: {tab_bar_items} tab bar items (max 5 recommended). More than 5 becomes hard to tap.")
|
|
216
|
+
|
|
217
|
+
# 3.2 Tab State Preservation Check
|
|
218
|
+
has_tab_nav = bool(re.search(r'createBottomTabNavigator|Tab\.Navigator', content))
|
|
219
|
+
if has_tab_nav:
|
|
220
|
+
# Look for lazy prop (false preserves state)
|
|
221
|
+
has_lazy_false = bool(re.search(r'lazy:\s*false', content))
|
|
222
|
+
if not has_lazy_false:
|
|
223
|
+
self.warnings.append(f"[Navigation] {filename}: Tab navigation without lazy: false. Tabs may lose state on switch.")
|
|
224
|
+
|
|
225
|
+
# 3.3 Back Handling Check
|
|
226
|
+
has_back_listener = bool(re.search(r'BackHandler|useFocusEffect|navigation\.addListener', content))
|
|
227
|
+
has_custom_back = bool(re.search(r'onBackPress|handleBackPress', content))
|
|
228
|
+
if has_custom_back and not has_back_listener:
|
|
229
|
+
self.warnings.append(f"[Navigation] {filename}: Custom back handling without BackHandler listener. May not work correctly.")
|
|
230
|
+
|
|
231
|
+
# 3.4 Deep Link Support Check
|
|
232
|
+
has_linking = bool(re.search(r'Linking\.|Linking\.openURL|deepLink|universalLink', content))
|
|
233
|
+
has_config = bool(re.search(r'apollo-link|react-native-screens|navigation\.link', content))
|
|
234
|
+
if not has_linking and not has_config:
|
|
235
|
+
self.passed_count += 1
|
|
236
|
+
else:
|
|
237
|
+
if has_linking and not has_config:
|
|
238
|
+
self.warnings.append(f"[Navigation] {filename}: Deep linking detected but may lack proper configuration. Test notification/share flows.")
|
|
239
|
+
|
|
240
|
+
# --- 4. MOBILE TYPOGRAPHY CHECKS ---
|
|
241
|
+
|
|
242
|
+
# 4.1 System Font Check
|
|
243
|
+
if is_react_native:
|
|
244
|
+
has_custom_font = bool(re.search(r"fontFamily:\s*[\"'][^\"']+", content))
|
|
245
|
+
has_system_font = bool(re.search(r"fontFamily:\s*[\"']?(?:System|San Francisco|Roboto|-apple-system)", content))
|
|
246
|
+
if has_custom_font and not has_system_font:
|
|
247
|
+
self.warnings.append(f"[Typography] {filename}: Custom font detected. Consider system fonts (iOS: SF Pro, Android: Roboto) for native feel.")
|
|
248
|
+
|
|
249
|
+
# 4.2 Text Scaling Check (iOS Dynamic Type)
|
|
250
|
+
if is_react_native:
|
|
251
|
+
has_font_sizes = bool(re.search(r'fontSize:', content))
|
|
252
|
+
has_scaling = bool(re.search(r'allowFontScaling:\s*true|responsiveFontSize|useWindowDimensions', content))
|
|
253
|
+
if has_font_sizes and not has_scaling:
|
|
254
|
+
self.warnings.append(f"[Typography] {filename}: Fixed font sizes without scaling support. Consider allowFontScaling for accessibility.")
|
|
255
|
+
|
|
256
|
+
# 4.3 Mobile Line Height Check
|
|
257
|
+
line_heights = re.findall(r'lineHeight:\s*([\d.]+)', content)
|
|
258
|
+
for lh in line_heights:
|
|
259
|
+
if float(lh) > 1.8:
|
|
260
|
+
self.warnings.append(f"[Typography] {filename}: lineHeight {lh} too high for mobile. Mobile text needs tighter spacing (1.3-1.5).")
|
|
261
|
+
|
|
262
|
+
# 4.4 Font Size Limits
|
|
263
|
+
font_sizes = re.findall(r'fontSize:\s*([\d.]+)', content)
|
|
264
|
+
for fs in font_sizes:
|
|
265
|
+
size = float(fs)
|
|
266
|
+
if size < 12:
|
|
267
|
+
self.warnings.append(f"[Typography] {filename}: fontSize {size}px below 12px minimum readability.")
|
|
268
|
+
elif size > 32:
|
|
269
|
+
self.warnings.append(f"[Typography] {filename}: fontSize {size}px very large. Consider using responsive scaling.")
|
|
270
|
+
|
|
271
|
+
# --- 5. MOBILE COLOR SYSTEM CHECKS ---
|
|
272
|
+
|
|
273
|
+
# 5.1 Pure Black Avoidance
|
|
274
|
+
if re.search(r'#000000|color:\s*black|backgroundColor:\s*["\']?black', content):
|
|
275
|
+
self.warnings.append(f"[Color] {filename}: Pure black (#000000) detected. Use dark gray (#1C1C1E iOS, #121212 Android) for better OLED/battery.")
|
|
276
|
+
|
|
277
|
+
# 5.2 Dark Mode Support
|
|
278
|
+
has_color_schemes = bool(re.search(r'useColorScheme|colorScheme|appearance:\s*["\']?dark', content))
|
|
279
|
+
has_dark_mode_style = bool(re.search(r'\\\?.*dark|style:\s*.*dark|isDark', content))
|
|
280
|
+
if not has_color_schemes and not has_dark_mode_style:
|
|
281
|
+
self.warnings.append(f"[Color] {filename}: No dark mode support detected. Consider useColorScheme for system dark mode.")
|
|
282
|
+
|
|
283
|
+
# --- 6. PLATFORM iOS CHECKS ---
|
|
284
|
+
|
|
285
|
+
if is_react_native:
|
|
286
|
+
# 6.1 SF Symbols Check
|
|
287
|
+
has_ios_icons = bool(re.search(r'@expo/vector-icons|ionicons', content))
|
|
288
|
+
has_sf_symbols = bool(re.search(r'sf-symbol|SF Symbols', content))
|
|
289
|
+
if has_ios_icons and not has_sf_symbols:
|
|
290
|
+
self.passed_count += 1
|
|
291
|
+
|
|
292
|
+
# 6.2 iOS Haptic Types
|
|
293
|
+
has_haptic_import = bool(re.search(r'expo-haptics|react-native-haptic-feedback', content))
|
|
294
|
+
has_haptic_types = bool(re.search(r'ImpactFeedback|NotificationFeedback|SelectionFeedback', content))
|
|
295
|
+
if has_haptic_import and not has_haptic_types:
|
|
296
|
+
self.warnings.append(f"[iOS Haptics] {filename}: Haptic library imported but not using typed haptics (Impact/Notification/Selection).")
|
|
297
|
+
|
|
298
|
+
# 6.3 iOS Safe Area
|
|
299
|
+
has_safe_area = bool(re.search(r'SafeAreaView|useSafeAreaInsets|safeArea', content))
|
|
300
|
+
if not has_safe_area:
|
|
301
|
+
self.warnings.append(f"[iOS] {filename}: No SafeArea detected. Content may be hidden by notch/home indicator.")
|
|
302
|
+
|
|
303
|
+
# --- 7. PLATFORM ANDROID CHECKS ---
|
|
304
|
+
|
|
305
|
+
if is_react_native:
|
|
306
|
+
# 7.1 Material Icons Check
|
|
307
|
+
has_material_icons = bool(re.search(r'@expo/vector-icons|MaterialIcons', content))
|
|
308
|
+
if has_material_icons:
|
|
309
|
+
self.passed_count += 1
|
|
310
|
+
|
|
311
|
+
# 7.2 Ripple Effect
|
|
312
|
+
has_ripple = bool(re.search(r'ripple|android_ripple|foregroundRipple', content))
|
|
313
|
+
has_pressable = bool(re.search(r'Pressable|Touchable', content))
|
|
314
|
+
if has_pressable and not has_ripple:
|
|
315
|
+
self.warnings.append(f"[Android] {filename}: Touchable without ripple effect. Android users expect ripple feedback.")
|
|
316
|
+
|
|
317
|
+
# 7.3 Hardware Back Button
|
|
318
|
+
if is_react_native:
|
|
319
|
+
has_back_button = bool(re.search(r'BackHandler|useBackHandler', content))
|
|
320
|
+
has_navigation = bool(re.search(r'@react-navigation', content))
|
|
321
|
+
if has_navigation and not has_back_button:
|
|
322
|
+
self.warnings.append(f"[Android] {filename}: React Navigation detected without BackHandler listener. Android hardware back may not work correctly.")
|
|
323
|
+
|
|
324
|
+
# --- 8. MOBILE BACKEND CHECKS ---
|
|
325
|
+
|
|
326
|
+
# 8.1 Secure Storage Check
|
|
327
|
+
has_async_storage = bool(re.search(r'AsyncStorage|@react-native-async-storage', content))
|
|
328
|
+
has_secure_storage = bool(re.search(r'SecureStore|Keychain|EncryptedSharedPreferences', content))
|
|
329
|
+
has_token_storage = bool(re.search(r'token|jwt|auth.*storage', content, re.IGNORECASE))
|
|
330
|
+
if has_token_storage and has_async_storage and not has_secure_storage:
|
|
331
|
+
self.issues.append(f"[Security] {filename}: Storing auth tokens in AsyncStorage (insecure). Use SecureStore (iOS) / EncryptedSharedPreferences (Android).")
|
|
332
|
+
|
|
333
|
+
# 8.2 Offline Handling Check
|
|
334
|
+
has_network = bool(re.search(r'fetch|axios|netinfo|@react-native-community/netinfo', content))
|
|
335
|
+
has_offline = bool(re.search(r'offline|isConnected|netInfo|cache.*offline', content))
|
|
336
|
+
if has_network and not has_offline:
|
|
337
|
+
self.warnings.append(f"[Offline] {filename}: Network requests detected without offline handling. Consider NetInfo for connection status.")
|
|
338
|
+
|
|
339
|
+
# 8.3 Push Notification Support
|
|
340
|
+
has_push = bool(re.search(r'Notifications|pushNotification|Firebase\.messaging|PushNotificationIOS', content))
|
|
341
|
+
has_push_handler = bool(re.search(r'onNotification|addNotificationListener|notification\.open', content))
|
|
342
|
+
if has_push and not has_push_handler:
|
|
343
|
+
self.warnings.append(f"[Push] {filename}: Push notifications imported but no handler found. May miss notifications.")
|
|
344
|
+
|
|
345
|
+
# --- 9. EXTENDED MOBILE TYPOGRAPHY CHECKS ---
|
|
346
|
+
|
|
347
|
+
# 9.1 iOS Type Scale Check
|
|
348
|
+
if is_react_native:
|
|
349
|
+
# Check for iOS text styles that match HIG
|
|
350
|
+
has_large_title = bool(re.search(r'fontSize:\s*34|largeTitle|font-weight:\s*["\']?bold', content))
|
|
351
|
+
has_title_1 = bool(re.search(r'fontSize:\s*28', content))
|
|
352
|
+
has_headline = bool(re.search(r'fontSize:\s*17.*semibold|headline', content))
|
|
353
|
+
has_body = bool(re.search(r'fontSize:\s*17.*regular|body', content))
|
|
354
|
+
|
|
355
|
+
# Check if following iOS scale roughly
|
|
356
|
+
font_sizes = re.findall(r'fontSize:\s*([\d.]+)', content)
|
|
357
|
+
ios_scale_sizes = [34, 28, 22, 20, 17, 16, 15, 13, 12, 11]
|
|
358
|
+
matching_ios = sum(1 for size in font_sizes if any(abs(float(size) - ios_size) < 1 for ios_size in ios_scale_sizes))
|
|
359
|
+
|
|
360
|
+
if len(font_sizes) > 3 and matching_ios < len(font_sizes) / 2:
|
|
361
|
+
self.warnings.append(f"[iOS Typography] {filename}: Font sizes don't match iOS type scale. Consider iOS text styles for native feel.")
|
|
362
|
+
|
|
363
|
+
# 9.2 Android Material Type Scale Check
|
|
364
|
+
if is_react_native:
|
|
365
|
+
# Check for Material 3 text styles
|
|
366
|
+
has_display = bool(re.search(r'fontSize:\s*[456][0-9]|display', content))
|
|
367
|
+
has_headline_material = bool(re.search(r'fontSize:\s*[23][0-9]|headline', content))
|
|
368
|
+
has_title_material = bool(re.search(r'fontSize:\s*2[12][0-9].*medium|title', content))
|
|
369
|
+
has_body_material = bool(re.search(r'fontSize:\s*1[456].*regular|body', content))
|
|
370
|
+
has_label = bool(re.search(r'fontSize:\s*1[1234].*medium|label', content))
|
|
371
|
+
|
|
372
|
+
# Check if using sp (scale-independent pixels)
|
|
373
|
+
uses_sp = bool(re.search(r'\d+\s*sp\b', content))
|
|
374
|
+
if has_display or has_headline_material:
|
|
375
|
+
if not uses_sp:
|
|
376
|
+
self.warnings.append(f"[Android Typography] {filename}: Material typography detected without sp units. Use sp for text to respect user font size preferences.")
|
|
377
|
+
|
|
378
|
+
# 9.3 Modular Scale Check
|
|
379
|
+
# Check if font sizes follow modular scale
|
|
380
|
+
font_sizes = re.findall(r'fontSize:\s*(\d+(?:\.\d+)?)', content)
|
|
381
|
+
if len(font_sizes) > 3:
|
|
382
|
+
sorted_sizes = sorted(set([float(s) for s in font_sizes]))
|
|
383
|
+
ratios = []
|
|
384
|
+
for i in range(1, len(sorted_sizes)):
|
|
385
|
+
if sorted_sizes[i-1] > 0:
|
|
386
|
+
ratios.append(sorted_sizes[i] / sorted_sizes[i-1])
|
|
387
|
+
|
|
388
|
+
# Common ratios: 1.125, 1.2, 1.25, 1.333, 1.5
|
|
389
|
+
common_ratios = {1.125, 1.2, 1.25, 1.333, 1.5}
|
|
390
|
+
for ratio in ratios[:3]:
|
|
391
|
+
if not any(abs(ratio - cr) < 0.03 for cr in common_ratios):
|
|
392
|
+
self.warnings.append(f"[Typography] {filename}: Font sizes may not follow modular scale (ratio: {ratio:.2f}). Consider consistent ratio.")
|
|
393
|
+
break
|
|
394
|
+
|
|
395
|
+
# 9.4 Line Length Check (Mobile-specific)
|
|
396
|
+
# Mobile text should be 40-60 characters max
|
|
397
|
+
if is_react_native:
|
|
398
|
+
has_long_text = bool(re.search(r'<Text[^>]*>[^<]{40,}', content))
|
|
399
|
+
has_max_width = bool(re.search(r'maxWidth|max-w-\d+|width:\s*["\']?\d+', content))
|
|
400
|
+
if has_long_text and not has_max_width:
|
|
401
|
+
self.warnings.append(f"[Mobile Typography] {filename}: Text without max-width constraint. Mobile text should be 40-60 characters per line for readability.")
|
|
402
|
+
|
|
403
|
+
# 9.5 Font Weight Pattern Check
|
|
404
|
+
# Check for font weight distribution
|
|
405
|
+
if is_react_native:
|
|
406
|
+
font_weights = re.findall(r'fontWeight:\s*["\']?(\d+|normal|bold|medium|light)', content)
|
|
407
|
+
weight_map = {'normal': '400', 'light': '300', 'medium': '500', 'bold': '700'}
|
|
408
|
+
numeric_weights = []
|
|
409
|
+
for w in font_weights:
|
|
410
|
+
val = weight_map.get(w.lower(), w)
|
|
411
|
+
try:
|
|
412
|
+
numeric_weights.append(int(val))
|
|
413
|
+
except:
|
|
414
|
+
pass
|
|
415
|
+
|
|
416
|
+
# Check if overusing bold (mobile should be regular-dominant)
|
|
417
|
+
bold_count = sum(1 for w in numeric_weights if w >= 700)
|
|
418
|
+
regular_count = sum(1 for w in numeric_weights if 400 <= w < 500)
|
|
419
|
+
if bold_count > regular_count:
|
|
420
|
+
self.warnings.append(f"[Mobile Typography] {filename}: More bold weights than regular. Mobile typography should be regular-dominant for readability.")
|
|
421
|
+
|
|
422
|
+
# --- 10. EXTENDED MOBILE COLOR SYSTEM CHECKS ---
|
|
423
|
+
|
|
424
|
+
# 10.1 OLED Optimization Check
|
|
425
|
+
# Check for near-black colors instead of pure black
|
|
426
|
+
if re.search(r'#121212|#1A1A1A|#0D0D0D', content):
|
|
427
|
+
self.passed_count += 1 # Good OLED optimization
|
|
428
|
+
elif re.search(r'backgroundColor:\s*["\']?#000000', content):
|
|
429
|
+
# Using pure black for background is OK for OLED
|
|
430
|
+
pass
|
|
431
|
+
elif re.search(r'backgroundColor:\s*["\']?#[0-9A-Fa-f]{6}', content):
|
|
432
|
+
# Check if using light colors in dark mode (bad for OLED)
|
|
433
|
+
self.warnings.append(f"[Mobile Color] {filename}: Consider OLED-optimized dark backgrounds (#121212 Android, #000000 iOS) for battery savings.")
|
|
434
|
+
|
|
435
|
+
# 10.2 Saturated Color Detection (Battery)
|
|
436
|
+
# Highly saturated colors consume more power on OLED
|
|
437
|
+
hex_colors = re.findall(r'#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})', content)
|
|
438
|
+
saturated_count = 0
|
|
439
|
+
for r, g, b in hex_colors:
|
|
440
|
+
# Convert to RGB 0-255
|
|
441
|
+
try:
|
|
442
|
+
r_val, g_val, b_val = int(r, 16), int(g, 16), int(b, 16)
|
|
443
|
+
max_val = max(r_val, g_val, b_val)
|
|
444
|
+
min_val = min(r_val, g_val, b_val)
|
|
445
|
+
# Saturation = (max - min) / max
|
|
446
|
+
if max_val > 0:
|
|
447
|
+
saturation = (max_val - min_val) / max_val
|
|
448
|
+
if saturation > 0.8: # Highly saturated
|
|
449
|
+
saturated_count += 1
|
|
450
|
+
except:
|
|
451
|
+
pass
|
|
452
|
+
|
|
453
|
+
if saturated_count > 10:
|
|
454
|
+
self.warnings.append(f"[Mobile Color] {filename}: {saturated_count} highly saturated colors detected. Desaturated colors save battery on OLED screens.")
|
|
455
|
+
|
|
456
|
+
# 10.3 Outdoor Visibility Check
|
|
457
|
+
# Low contrast combinations fail in outdoor sunlight
|
|
458
|
+
light_colors = re.findall(r'#[0-9A-Fa-f]{6}|rgba?\([^)]+\)', content)
|
|
459
|
+
# Check for potential low contrast (light gray on white, dark gray on black)
|
|
460
|
+
potential_low_contrast = bool(re.search(r'#[EeEeEeEe].*#ffffff|#999999.*#ffffff|#333333.*#000000|#666666.*#000000', content))
|
|
461
|
+
if potential_low_contrast:
|
|
462
|
+
self.warnings.append(f"[Mobile Color] {filename}: Possible low contrast combination detected. Critical for outdoor visibility. Ensure WCAG AAA (7:1) for mobile.")
|
|
463
|
+
|
|
464
|
+
# 10.4 Dark Mode Text Color Check
|
|
465
|
+
# In dark mode, text should not be pure white
|
|
466
|
+
has_dark_mode = bool(re.search(r'dark:\s*|isDark|useColorScheme|colorScheme:\s*["\']?dark', content))
|
|
467
|
+
if has_dark_mode:
|
|
468
|
+
has_pure_white_text = bool(re.search(r'color:\s*["\']?#ffffff|#fff["\']?\}|textColor:\s*["\']?white', content))
|
|
469
|
+
if has_pure_white_text:
|
|
470
|
+
self.warnings.append(f"[Mobile Color] {filename}: Pure white text (#FFFFFF) in dark mode. Use #E8E8E8 or light gray for better readability.")
|
|
471
|
+
|
|
472
|
+
# --- 11. EXTENDED PLATFORM IOS CHECKS ---
|
|
473
|
+
|
|
474
|
+
if is_react_native:
|
|
475
|
+
# 11.1 SF Pro Font Detection
|
|
476
|
+
has_sf_pro = bool(re.search(r'SF Pro|SFPro|fontFamily:\s*["\']?[-\s]*SF', content))
|
|
477
|
+
has_custom_font = bool(re.search(r'fontFamily:\s*["\'][^"\']+', content))
|
|
478
|
+
if has_custom_font and not has_sf_pro:
|
|
479
|
+
self.warnings.append(f"[iOS] {filename}: Custom font without SF Pro fallback. Consider SF Pro Text for body, SF Pro Display for headings.")
|
|
480
|
+
|
|
481
|
+
# 11.2 iOS System Colors Check
|
|
482
|
+
# Check for semantic color usage
|
|
483
|
+
has_label = bool(re.search(r'color:\s*["\']?label|\.label', content))
|
|
484
|
+
has_secondaryLabel = bool(re.search(r'secondaryLabel|\.secondaryLabel', content))
|
|
485
|
+
has_systemBackground = bool(re.search(r'systemBackground|\.systemBackground', content))
|
|
486
|
+
|
|
487
|
+
has_hardcoded_gray = bool(re.search(r'#[78]0{4}', content))
|
|
488
|
+
if has_hardcoded_gray and not (has_label or has_secondaryLabel):
|
|
489
|
+
self.warnings.append(f"[iOS] {filename}: Hardcoded gray colors detected. Consider iOS semantic colors (label, secondaryLabel) for automatic dark mode.")
|
|
490
|
+
|
|
491
|
+
# 11.3 iOS Accent Colors Check
|
|
492
|
+
ios_blue = bool(re.search(r'#007AFF|#0A84FF|systemBlue', content))
|
|
493
|
+
ios_green = bool(re.search(r'#34C759|#30D158|systemGreen', content))
|
|
494
|
+
ios_red = bool(re.search(r'#FF3B30|#FF453A|systemRed', content))
|
|
495
|
+
|
|
496
|
+
has_custom_primary = bool(re.search(r'primaryColor|theme.*primary|colors\.primary', content))
|
|
497
|
+
if has_custom_primary and not (ios_blue or ios_green or ios_red):
|
|
498
|
+
self.warnings.append(f"[iOS] {filename}: Custom primary color without iOS system color fallback. Consider systemBlue for consistent iOS feel.")
|
|
499
|
+
|
|
500
|
+
# 11.4 iOS Navigation Patterns Check
|
|
501
|
+
has_navigation_bar = bool(re.search(r'navigationOptions|headerStyle|cardStyle', content))
|
|
502
|
+
has_header_title = bool(re.search(r'title:\s*["\']|headerTitle|navigation\.setOptions', content))
|
|
503
|
+
if has_navigation_bar and not has_header_title:
|
|
504
|
+
self.warnings.append(f"[iOS] {filename}: Navigation bar detected without title. iOS apps should have clear context in nav bar.")
|
|
505
|
+
|
|
506
|
+
# 11.5 iOS Component Patterns Check
|
|
507
|
+
# Check for iOS-specific components
|
|
508
|
+
has_alert = bool(re.search(r'Alert\.alert|showAlert', content))
|
|
509
|
+
has_action_sheet = bool(re.search(r'ActionSheet|ActionSheetIOS|showActionSheetWithOptions', content))
|
|
510
|
+
has_activity_indicator = bool(re.search(r'ActivityIndicator|ActivityIndic', content))
|
|
511
|
+
|
|
512
|
+
if has_alert or has_action_sheet or has_activity_indicator:
|
|
513
|
+
self.passed_count += 1 # Good iOS component usage
|
|
514
|
+
|
|
515
|
+
# --- 12. EXTENDED PLATFORM ANDROID CHECKS ---
|
|
516
|
+
|
|
517
|
+
if is_react_native:
|
|
518
|
+
# 12.1 Roboto Font Detection
|
|
519
|
+
has_roboto = bool(re.search(r'Roboto|fontFamily:\s*["\']?[-\s]*Roboto', content))
|
|
520
|
+
has_custom_font = bool(re.search(r'fontFamily:\s*["\'][^"\']+', content))
|
|
521
|
+
if has_custom_font and not has_roboto:
|
|
522
|
+
self.warnings.append(f"[Android] {filename}: Custom font without Roboto fallback. Roboto is optimized for Android displays.")
|
|
523
|
+
|
|
524
|
+
# 12.2 Material 3 Dynamic Color Check
|
|
525
|
+
has_material_colors = bool(re.search(r'MD3|MaterialYou|dynamicColor|useColorScheme', content))
|
|
526
|
+
has_theme_provider = bool(re.search(r'MaterialTheme|ThemeProvider|PaperProvider|ThemeProvider', content))
|
|
527
|
+
if not has_material_colors and not has_theme_provider:
|
|
528
|
+
self.warnings.append(f"[Android] {filename}: No Material 3 dynamic color detected. Consider Material 3 theming for personalized feel.")
|
|
529
|
+
|
|
530
|
+
# 12.3 Material Elevation Check
|
|
531
|
+
# Check for elevation values (Material 3 uses elevation for depth)
|
|
532
|
+
has_elevation = bool(re.search(r'elevation:\s*\d+|shadowOpacity|shadowRadius|android:elevation', content))
|
|
533
|
+
has_box_shadow = bool(re.search(r'boxShadow:', content))
|
|
534
|
+
if has_box_shadow and not has_elevation:
|
|
535
|
+
self.warnings.append(f"[Android] {filename}: CSS box-shadow detected without elevation. Consider Material elevation system for consistent depth.")
|
|
536
|
+
|
|
537
|
+
# 12.4 Material Component Patterns Check
|
|
538
|
+
# Check for Material components
|
|
539
|
+
has_ripple = bool(re.search(r'ripple|android_ripple|foregroundRipple', content))
|
|
540
|
+
has_card = bool(re.search(r'Card|Paper|elevation.*\d+', content))
|
|
541
|
+
has_fab = bool(re.search(r'FAB|FloatingActionButton|fab', content))
|
|
542
|
+
has_snackbar = bool(re.search(r'Snackbar|showSnackBar|Toast', content))
|
|
543
|
+
|
|
544
|
+
material_component_count = sum([has_ripple, has_card, has_fab, has_snackbar])
|
|
545
|
+
if material_component_count >= 2:
|
|
546
|
+
self.passed_count += 1 # Good Material design usage
|
|
547
|
+
|
|
548
|
+
# 12.5 Android Navigation Patterns Check
|
|
549
|
+
has_top_app_bar = bool(re.search(r'TopAppBar|AppBar|CollapsingToolbar', content))
|
|
550
|
+
has_bottom_nav = bool(re.search(r'BottomNavigation|BottomNav', content))
|
|
551
|
+
has_navigation_rail = bool(re.search(r'NavigationRail', content))
|
|
552
|
+
|
|
553
|
+
if has_bottom_nav:
|
|
554
|
+
self.passed_count += 1 # Good Android pattern
|
|
555
|
+
elif has_top_app_bar and not (has_bottom_nav or has_navigation_rail):
|
|
556
|
+
self.warnings.append(f"[Android] {filename}: TopAppBar without bottom navigation. Consider BottomNavigation for thumb-friendly access.")
|
|
557
|
+
|
|
558
|
+
# --- 13. MOBILE TESTING CHECKS ---
|
|
559
|
+
|
|
560
|
+
# 13.1 Testing Tool Detection
|
|
561
|
+
has_rntl = bool(re.search(r'react-native-testing-library|@testing-library', content))
|
|
562
|
+
has_detox = bool(re.search(r'detox|element\(|by\.text|by\.id', content))
|
|
563
|
+
has_maestro = bool(re.search(r'maestro|\.yaml$', content))
|
|
564
|
+
has_jest = bool(re.search(r'jest|describe\(|test\(|it\(', content))
|
|
565
|
+
|
|
566
|
+
testing_tools = []
|
|
567
|
+
if has_jest: testing_tools.append('Jest')
|
|
568
|
+
if has_rntl: testing_tools.append('RNTL')
|
|
569
|
+
if has_detox: testing_tools.append('Detox')
|
|
570
|
+
if has_maestro: testing_tools.append('Maestro')
|
|
571
|
+
|
|
572
|
+
if len(testing_tools) == 0:
|
|
573
|
+
self.warnings.append(f"[Testing] {filename}: No testing framework detected. Consider Jest (unit) + Detox/Maestro (E2E) for mobile.")
|
|
574
|
+
|
|
575
|
+
# 13.2 Test Pyramid Balance Check
|
|
576
|
+
test_files = len(re.findall(r'\.test\.(tsx|ts|js|jsx)|\.spec\.', content))
|
|
577
|
+
e2e_tests = len(re.findall(r'detox|maestro|e2e|spec\.e2e', content.lower()))
|
|
578
|
+
|
|
579
|
+
if test_files > 0 and e2e_tests == 0:
|
|
580
|
+
self.warnings.append(f"[Testing] {filename}: Unit tests found but no E2E tests. Mobile needs E2E on real devices for complete coverage.")
|
|
581
|
+
|
|
582
|
+
# 13.3 Accessibility Label Check (Mobile-specific)
|
|
583
|
+
if is_react_native:
|
|
584
|
+
has_pressable = bool(re.search(r'Pressable|TouchableOpacity|TouchableHighlight', content))
|
|
585
|
+
has_a11y_label = bool(re.search(r'accessibilityLabel|aria-label|testID', content))
|
|
586
|
+
if has_pressable and not has_a11y_label:
|
|
587
|
+
self.warnings.append(f"[A11y Mobile] {filename}: Touchable element without accessibilityLabel. Screen readers need labels for all interactive elements.")
|
|
588
|
+
|
|
589
|
+
# --- 14. MOBILE DEBUGGING CHECKS ---
|
|
590
|
+
|
|
591
|
+
# 14.1 Performance Profiling Check
|
|
592
|
+
has_performance = bool(re.search(r'Performance|systrace|profile|Flipper', content))
|
|
593
|
+
has_console_log = len(re.findall(r'console\.(log|warn|error|debug|info)', content))
|
|
594
|
+
has_debugger = bool(re.search(r'debugger|__DEV__|React\.DevTools', content))
|
|
595
|
+
|
|
596
|
+
if has_console_log > 10:
|
|
597
|
+
self.warnings.append(f"[Debugging] {filename}: {has_console_log} console.log statements. Remove before production; they block JS thread.")
|
|
598
|
+
|
|
599
|
+
if has_performance:
|
|
600
|
+
self.passed_count += 1 # Good performance monitoring
|
|
601
|
+
|
|
602
|
+
# 14.2 Error Boundary Check
|
|
603
|
+
has_error_boundary = bool(re.search(r'ErrorBoundary|componentDidCatch|getDerivedStateFromError', content))
|
|
604
|
+
if not has_error_boundary and is_react_native:
|
|
605
|
+
self.warnings.append(f"[Debugging] {filename}: No ErrorBoundary detected. Consider adding ErrorBoundary to prevent app crashes.")
|
|
606
|
+
|
|
607
|
+
# 14.3 Hermes Check (React Native specific)
|
|
608
|
+
if is_react_native:
|
|
609
|
+
# Check if using Hermes engine (should be default in modern RN)
|
|
610
|
+
# This is more of a configuration check, not code pattern
|
|
611
|
+
self.passed_count += 1 # Hermes is default in RN 0.70+
|
|
612
|
+
|
|
613
|
+
def audit_directory(self, directory: str) -> None:
|
|
614
|
+
extensions = {'.tsx', '.ts', '.jsx', '.js', '.dart'}
|
|
615
|
+
for root, dirs, files in os.walk(directory):
|
|
616
|
+
dirs[:] = [d for d in dirs if d not in {'node_modules', '.git', 'dist', 'build', '.next', 'ios', 'android', 'build', '.idea'}]
|
|
617
|
+
for file in files:
|
|
618
|
+
if Path(file).suffix in extensions:
|
|
619
|
+
self.audit_file(os.path.join(root, file))
|
|
620
|
+
|
|
621
|
+
def get_report(self):
|
|
622
|
+
return {
|
|
623
|
+
"files_checked": self.files_checked,
|
|
624
|
+
"issues": self.issues,
|
|
625
|
+
"warnings": self.warnings,
|
|
626
|
+
"passed_checks": self.passed_count,
|
|
627
|
+
"compliant": len(self.issues) == 0
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def main():
|
|
632
|
+
if len(sys.argv) < 2:
|
|
633
|
+
print("Usage: python mobile_audit.py <directory>")
|
|
634
|
+
sys.exit(1)
|
|
635
|
+
|
|
636
|
+
path = sys.argv[1]
|
|
637
|
+
is_json = "--json" in sys.argv
|
|
638
|
+
|
|
639
|
+
auditor = MobileAuditor()
|
|
640
|
+
if os.path.isfile(path):
|
|
641
|
+
auditor.audit_file(path)
|
|
642
|
+
else:
|
|
643
|
+
auditor.audit_directory(path)
|
|
644
|
+
|
|
645
|
+
report = auditor.get_report()
|
|
646
|
+
|
|
647
|
+
if is_json:
|
|
648
|
+
print(json.dumps(report, indent=2))
|
|
649
|
+
else:
|
|
650
|
+
print(f"\n[MOBILE AUDIT] {report['files_checked']} mobile files checked")
|
|
651
|
+
print("-" * 50)
|
|
652
|
+
if report['issues']:
|
|
653
|
+
print(f"[!] ISSUES ({len(report['issues'])}):")
|
|
654
|
+
for i in report['issues'][:10]:
|
|
655
|
+
print(f" - {i}")
|
|
656
|
+
if report['warnings']:
|
|
657
|
+
print(f"[*] WARNINGS ({len(report['warnings'])}):")
|
|
658
|
+
for w in report['warnings'][:15]:
|
|
659
|
+
print(f" - {w}")
|
|
660
|
+
print(f"[+] PASSED CHECKS: {report['passed_checks']}")
|
|
661
|
+
status = "PASS" if report['compliant'] else "FAIL"
|
|
662
|
+
print(f"STATUS: {status}")
|
|
663
|
+
|
|
664
|
+
sys.exit(0 if report['compliant'] else 1)
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
if __name__ == "__main__":
|
|
668
|
+
# Fix missing import
|
|
669
|
+
import re
|
|
670
|
+
main()
|