flutter-pro-max-cli 2.0.0 → 2.1.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/README.md +12 -0
- package/assets/scripts/__pycache__/core.cpython-312.pyc +0 -0
- package/assets/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
- package/assets/scripts/core.py +52 -45
- package/assets/scripts/design_system.py +1074 -0
- package/assets/scripts/search.py +72 -1
- package/assets/templates/platforms/agent.json +1 -1
- package/assets/templates/platforms/claude.json +3 -3
- package/assets/templates/platforms/codebuddy.json +12 -9
- package/assets/templates/platforms/codex.json +6 -6
- package/assets/templates/platforms/continue.json +5 -5
- package/assets/templates/platforms/copilot.json +6 -6
- package/assets/templates/platforms/cursor.json +5 -5
- package/assets/templates/platforms/gemini.json +6 -6
- package/assets/templates/platforms/kiro.json +5 -5
- package/assets/templates/platforms/opencode.json +6 -6
- package/assets/templates/platforms/qoder.json +11 -8
- package/assets/templates/platforms/roocode.json +6 -6
- package/assets/templates/platforms/trae.json +6 -6
- package/assets/templates/platforms/windsurf.json +6 -6
- package/package.json +1 -1
|
@@ -0,0 +1,1074 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Flutter Pro Max Design System Generator - Aggregates search results and applies reasoning
|
|
5
|
+
to generate comprehensive design system recommendations for Flutter apps.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from design_system import generate_design_system
|
|
9
|
+
result = generate_design_system("fintech banking app", "MyApp")
|
|
10
|
+
|
|
11
|
+
# With persistence (Master + Overrides pattern)
|
|
12
|
+
result = generate_design_system("fintech banking app", "MyApp", persist=True)
|
|
13
|
+
result = generate_design_system("fintech banking app", "MyApp", persist=True, page="dashboard")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import csv
|
|
17
|
+
import json
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
from core import search, DATA_DIR
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# ============ CONFIGURATION ============
|
|
25
|
+
REASONING_FILE = "ui-reasoning.csv"
|
|
26
|
+
|
|
27
|
+
SEARCH_CONFIG = {
|
|
28
|
+
"product": {"max_results": 1},
|
|
29
|
+
"style": {"max_results": 3},
|
|
30
|
+
"color": {"max_results": 2},
|
|
31
|
+
"typography": {"max_results": 2},
|
|
32
|
+
"pattern": {"max_results": 3},
|
|
33
|
+
"architect": {"max_results": 2},
|
|
34
|
+
"landing": {"max_results": 2} # Landing page patterns for screen structure
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ============ DESIGN SYSTEM GENERATOR ============
|
|
39
|
+
class DesignSystemGenerator:
|
|
40
|
+
"""Generates design system recommendations from aggregated searches."""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
self.reasoning_data: list[dict[str, str]] = self._load_reasoning()
|
|
44
|
+
|
|
45
|
+
def _load_reasoning(self) -> list[dict[str, str]]:
|
|
46
|
+
"""Load reasoning rules from CSV."""
|
|
47
|
+
filepath = DATA_DIR / REASONING_FILE
|
|
48
|
+
if not filepath.exists():
|
|
49
|
+
return []
|
|
50
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
51
|
+
return list(csv.DictReader(f))
|
|
52
|
+
|
|
53
|
+
def _multi_domain_search(self, query: str, style_priority: list[str] | None = None) -> dict[str, Any]:
|
|
54
|
+
"""Execute searches across multiple domains."""
|
|
55
|
+
results: dict[str, Any] = {}
|
|
56
|
+
for domain, config in SEARCH_CONFIG.items():
|
|
57
|
+
if domain == "style" and style_priority:
|
|
58
|
+
# For style, also search with priority keywords
|
|
59
|
+
priority_query = " ".join(style_priority[:2]) if style_priority else query
|
|
60
|
+
combined_query = f"{query} {priority_query}"
|
|
61
|
+
results[domain] = search(combined_query, domain, config["max_results"])
|
|
62
|
+
else:
|
|
63
|
+
results[domain] = search(query, domain, config["max_results"])
|
|
64
|
+
return results
|
|
65
|
+
|
|
66
|
+
def _find_reasoning_rule(self, category: str) -> dict[str, str]:
|
|
67
|
+
"""Find matching reasoning rule for a category."""
|
|
68
|
+
category_lower = category.lower()
|
|
69
|
+
|
|
70
|
+
# Try exact match first
|
|
71
|
+
for rule in self.reasoning_data:
|
|
72
|
+
if rule.get("App_Category", "").lower() == category_lower:
|
|
73
|
+
return rule
|
|
74
|
+
|
|
75
|
+
# Try partial match
|
|
76
|
+
for rule in self.reasoning_data:
|
|
77
|
+
app_cat = rule.get("App_Category", "").lower()
|
|
78
|
+
if app_cat in category_lower or category_lower in app_cat:
|
|
79
|
+
return rule
|
|
80
|
+
|
|
81
|
+
# Try keyword match
|
|
82
|
+
for rule in self.reasoning_data:
|
|
83
|
+
app_cat = rule.get("App_Category", "").lower()
|
|
84
|
+
keywords = app_cat.replace("/", " ").replace("-", " ").split()
|
|
85
|
+
if any(kw in category_lower for kw in keywords):
|
|
86
|
+
return rule
|
|
87
|
+
|
|
88
|
+
return {}
|
|
89
|
+
|
|
90
|
+
def _apply_reasoning(self, category: str, search_results: dict[str, Any]) -> dict[str, Any]:
|
|
91
|
+
"""Apply reasoning rules to search results with intelligent decision processing."""
|
|
92
|
+
rule = self._find_reasoning_rule(category)
|
|
93
|
+
|
|
94
|
+
if not rule:
|
|
95
|
+
return {
|
|
96
|
+
"pattern": "Clean Architecture + Feature-First",
|
|
97
|
+
"style_priority": ["Minimalism", "Flat Design"],
|
|
98
|
+
"color_mood": "Professional",
|
|
99
|
+
"typography_mood": "Clean",
|
|
100
|
+
"key_effects": "Subtle animations, smooth transitions",
|
|
101
|
+
"anti_patterns": "",
|
|
102
|
+
"decision_rules": {},
|
|
103
|
+
"severity": "MEDIUM",
|
|
104
|
+
"must_have_features": [],
|
|
105
|
+
"conversion_focus": ""
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Parse decision rules JSON and extract must_have features
|
|
109
|
+
decision_rules: dict[str, Any] = {}
|
|
110
|
+
must_have_features: list[Any] = []
|
|
111
|
+
try:
|
|
112
|
+
decision_rules = json.loads(str(rule.get("Decision_Rules", "{}")))
|
|
113
|
+
# Extract must_have from decision rules
|
|
114
|
+
for key, value in decision_rules.items():
|
|
115
|
+
if key == "must_have" or key.startswith("must_have"):
|
|
116
|
+
must_have_features.append(value)
|
|
117
|
+
except json.JSONDecodeError:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
# Determine conversion focus based on category
|
|
121
|
+
conversion_focus = self._determine_conversion_focus(category)
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
"pattern": rule.get("Recommended_Pattern", ""),
|
|
125
|
+
"style_priority": [s.strip() for s in rule.get("Style_Priority", "").split("+")],
|
|
126
|
+
"color_mood": rule.get("Color_Mood", ""),
|
|
127
|
+
"typography_mood": rule.get("Typography_Mood", ""),
|
|
128
|
+
"key_effects": rule.get("Key_Effects", ""),
|
|
129
|
+
"anti_patterns": rule.get("Anti_Patterns", ""),
|
|
130
|
+
"decision_rules": decision_rules,
|
|
131
|
+
"severity": rule.get("Severity", "MEDIUM"),
|
|
132
|
+
"must_have_features": must_have_features,
|
|
133
|
+
"conversion_focus": conversion_focus
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def _determine_conversion_focus(self, category: str) -> str:
|
|
137
|
+
"""Determine conversion focus based on app category."""
|
|
138
|
+
category_lower = category.lower()
|
|
139
|
+
|
|
140
|
+
conversion_map: dict[str, str] = {
|
|
141
|
+
"e-commerce": "Purchase conversion, Add to cart, Quick checkout",
|
|
142
|
+
"fintech": "Trust building, Security perception, Transaction completion",
|
|
143
|
+
"banking": "Trust building, Security perception, Transaction completion",
|
|
144
|
+
"health": "Appointment booking, Trust signals, Accessibility",
|
|
145
|
+
"fitness": "Engagement, Progress motivation, Habit formation",
|
|
146
|
+
"social": "Engagement, Retention, Content sharing",
|
|
147
|
+
"education": "Progress tracking, Completion rates, Engagement",
|
|
148
|
+
"productivity": "Task completion, Efficiency, Quick actions",
|
|
149
|
+
"food": "Order conversion, Reorder, Quick checkout",
|
|
150
|
+
"travel": "Booking conversion, Search efficiency, Trust",
|
|
151
|
+
"gaming": "Engagement, Retention, In-app purchases",
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for key, focus in conversion_map.items():
|
|
155
|
+
if key in category_lower:
|
|
156
|
+
return focus
|
|
157
|
+
|
|
158
|
+
return "User engagement and task completion"
|
|
159
|
+
|
|
160
|
+
def _select_best_match(self, results: list[dict[str, Any]], priority_keywords: list[str]) -> dict[str, Any]:
|
|
161
|
+
"""Select best matching result based on priority keywords."""
|
|
162
|
+
if not results:
|
|
163
|
+
return {}
|
|
164
|
+
|
|
165
|
+
if not priority_keywords:
|
|
166
|
+
return results[0]
|
|
167
|
+
|
|
168
|
+
# First: try exact style name match
|
|
169
|
+
for priority in priority_keywords:
|
|
170
|
+
priority_lower = priority.lower().strip()
|
|
171
|
+
for result in results:
|
|
172
|
+
style_name = result.get("Style Category", "").lower()
|
|
173
|
+
if priority_lower in style_name or style_name in priority_lower:
|
|
174
|
+
return result
|
|
175
|
+
|
|
176
|
+
# Second: score by keyword match in all fields
|
|
177
|
+
scored: list[tuple[int, dict[str, Any]]] = []
|
|
178
|
+
for result in results:
|
|
179
|
+
result_str = str(result).lower()
|
|
180
|
+
score = 0
|
|
181
|
+
for kw in priority_keywords:
|
|
182
|
+
kw_lower = kw.lower().strip()
|
|
183
|
+
# Higher score for style name match
|
|
184
|
+
if kw_lower in result.get("Style Category", "").lower():
|
|
185
|
+
score += 10
|
|
186
|
+
# Lower score for keyword field match
|
|
187
|
+
elif kw_lower in result.get("Keywords", "").lower():
|
|
188
|
+
score += 3
|
|
189
|
+
# Even lower for other field matches
|
|
190
|
+
elif kw_lower in result_str:
|
|
191
|
+
score += 1
|
|
192
|
+
scored.append((score, result))
|
|
193
|
+
|
|
194
|
+
scored.sort(key=lambda x: x[0], reverse=True)
|
|
195
|
+
return scored[0][1] if scored and scored[0][0] > 0 else results[0]
|
|
196
|
+
|
|
197
|
+
def _extract_results(self, search_result: dict[str, Any]) -> list[dict[str, Any]]:
|
|
198
|
+
"""Extract results list from search result dict."""
|
|
199
|
+
return search_result.get("results", [])
|
|
200
|
+
|
|
201
|
+
def generate(self, query: str, project_name: str | None = None) -> dict[str, Any]:
|
|
202
|
+
"""Generate complete design system recommendation with landing pattern intelligence."""
|
|
203
|
+
# Step 1: First search product to get category
|
|
204
|
+
product_result = search(query, "product", 1)
|
|
205
|
+
product_results = product_result.get("results", [])
|
|
206
|
+
category = "General"
|
|
207
|
+
product_info = {}
|
|
208
|
+
if product_results:
|
|
209
|
+
product_info = product_results[0]
|
|
210
|
+
category = product_info.get("Product Type", "General")
|
|
211
|
+
|
|
212
|
+
# Step 2: Get reasoning rules for this category
|
|
213
|
+
reasoning = self._apply_reasoning(category, {})
|
|
214
|
+
style_priority = reasoning.get("style_priority", [])
|
|
215
|
+
|
|
216
|
+
# Step 3: Multi-domain search with style priority hints
|
|
217
|
+
search_results = self._multi_domain_search(query, style_priority)
|
|
218
|
+
search_results["product"] = product_result # Reuse product search
|
|
219
|
+
|
|
220
|
+
# Step 4: Select best matches from each domain using priority
|
|
221
|
+
style_results = self._extract_results(search_results.get("style", {}))
|
|
222
|
+
color_results = self._extract_results(search_results.get("color", {}))
|
|
223
|
+
typography_results = self._extract_results(search_results.get("typography", {}))
|
|
224
|
+
pattern_results = self._extract_results(search_results.get("pattern", {}))
|
|
225
|
+
architect_results = self._extract_results(search_results.get("architect", {}))
|
|
226
|
+
landing_results = self._extract_results(search_results.get("landing", {}))
|
|
227
|
+
|
|
228
|
+
best_style = self._select_best_match(style_results, reasoning.get("style_priority", []))
|
|
229
|
+
best_color = color_results[0] if color_results else {}
|
|
230
|
+
best_typography = typography_results[0] if typography_results else {}
|
|
231
|
+
best_landing = landing_results[0] if landing_results else {}
|
|
232
|
+
|
|
233
|
+
# Step 5: Build final recommendation
|
|
234
|
+
# Combine effects from both reasoning and style search
|
|
235
|
+
style_effects = best_style.get("Effects & Animation", "")
|
|
236
|
+
reasoning_effects = reasoning.get("key_effects", "")
|
|
237
|
+
combined_effects = style_effects if style_effects else reasoning_effects
|
|
238
|
+
|
|
239
|
+
# Get landing pattern info
|
|
240
|
+
landing_effects = best_landing.get("Recommended Effects", "")
|
|
241
|
+
if landing_effects and not combined_effects:
|
|
242
|
+
combined_effects = landing_effects
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
"project_name": project_name or query.upper(),
|
|
246
|
+
"category": category,
|
|
247
|
+
"pattern": {
|
|
248
|
+
"name": reasoning.get("pattern", "Clean Architecture"),
|
|
249
|
+
"architecture": architect_results[0].get("layer", "") if architect_results else "Feature-First",
|
|
250
|
+
"state_management": "Riverpod / BLoC",
|
|
251
|
+
"recommended_patterns": [p.get("pattern_name", "") for p in pattern_results[:3]]
|
|
252
|
+
},
|
|
253
|
+
# NEW: Landing/Screen pattern from UI-UX Pro Max
|
|
254
|
+
"screen_pattern": {
|
|
255
|
+
"name": best_landing.get("Pattern Name", "Hero + Features + CTA"),
|
|
256
|
+
"sections": best_landing.get("Section Order", "Hero > Features > CTA"),
|
|
257
|
+
"cta_placement": best_landing.get("Primary CTA Placement", "Bottom + Sticky"),
|
|
258
|
+
"color_strategy": best_landing.get("Color Strategy", ""),
|
|
259
|
+
"conversion_optimization": best_landing.get("Conversion Optimization", "")
|
|
260
|
+
},
|
|
261
|
+
"style": {
|
|
262
|
+
"name": best_style.get("Style Category", "Minimalism"),
|
|
263
|
+
"type": best_style.get("Type", "General"),
|
|
264
|
+
"effects": style_effects,
|
|
265
|
+
"keywords": best_style.get("Keywords", ""),
|
|
266
|
+
"best_for": best_style.get("Best For", ""),
|
|
267
|
+
"do_not_use": best_style.get("Do Not Use For", "")
|
|
268
|
+
},
|
|
269
|
+
"colors": {
|
|
270
|
+
"primary": best_color.get("Primary (Hex)", "#2563EB"),
|
|
271
|
+
"secondary": best_color.get("Secondary (Hex)", "#3B82F6"),
|
|
272
|
+
"cta": best_color.get("CTA (Hex)", "#F97316"),
|
|
273
|
+
"background": "#FFFFFF",
|
|
274
|
+
"surface": "#F8FAFC",
|
|
275
|
+
"text": "#1E293B",
|
|
276
|
+
"notes": best_color.get("Notes", ""),
|
|
277
|
+
"strategy": best_landing.get("Color Strategy", reasoning.get("color_mood", ""))
|
|
278
|
+
},
|
|
279
|
+
"typography": {
|
|
280
|
+
"heading": best_typography.get("Heading Font", "Inter"),
|
|
281
|
+
"body": best_typography.get("Body Font", "Inter"),
|
|
282
|
+
"mood": best_typography.get("Mood/Style Keywords", reasoning.get("typography_mood", "")),
|
|
283
|
+
"best_for": best_typography.get("Best For", ""),
|
|
284
|
+
"google_fonts_url": best_typography.get("Google Fonts URL", "")
|
|
285
|
+
},
|
|
286
|
+
"key_effects": combined_effects,
|
|
287
|
+
"anti_patterns": reasoning.get("anti_patterns", ""),
|
|
288
|
+
"decision_rules": reasoning.get("decision_rules", {}),
|
|
289
|
+
"severity": reasoning.get("severity", "MEDIUM"),
|
|
290
|
+
# NEW: Conversion and must-have features
|
|
291
|
+
"conversion_focus": reasoning.get("conversion_focus", ""),
|
|
292
|
+
"must_have_features": reasoning.get("must_have_features", [])
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# ============ OUTPUT FORMATTERS ============
|
|
297
|
+
BOX_WIDTH = 90
|
|
298
|
+
|
|
299
|
+
def format_ascii_box(design_system: dict[str, Any]) -> str:
|
|
300
|
+
"""Format design system as ASCII box for Flutter apps with screen pattern intelligence."""
|
|
301
|
+
project = design_system.get("project_name", "PROJECT")
|
|
302
|
+
pattern = design_system.get("pattern", {})
|
|
303
|
+
screen_pattern = design_system.get("screen_pattern", {})
|
|
304
|
+
style = design_system.get("style", {})
|
|
305
|
+
colors = design_system.get("colors", {})
|
|
306
|
+
typography = design_system.get("typography", {})
|
|
307
|
+
effects = design_system.get("key_effects", "")
|
|
308
|
+
anti_patterns = design_system.get("anti_patterns", "")
|
|
309
|
+
conversion_focus = design_system.get("conversion_focus", "")
|
|
310
|
+
must_have_features = design_system.get("must_have_features", [])
|
|
311
|
+
|
|
312
|
+
def wrap_text(text: str, prefix: str, width: int) -> list[str]:
|
|
313
|
+
"""Wrap long text into multiple lines."""
|
|
314
|
+
if not text:
|
|
315
|
+
return []
|
|
316
|
+
words = text.split()
|
|
317
|
+
lines: list[str] = []
|
|
318
|
+
current_line = prefix
|
|
319
|
+
for word in words:
|
|
320
|
+
if len(current_line) + len(word) + 1 <= width - 2:
|
|
321
|
+
current_line += (" " if current_line != prefix else "") + word
|
|
322
|
+
else:
|
|
323
|
+
if current_line != prefix:
|
|
324
|
+
lines.append(current_line)
|
|
325
|
+
current_line = prefix + word
|
|
326
|
+
if current_line != prefix:
|
|
327
|
+
lines.append(current_line)
|
|
328
|
+
return lines
|
|
329
|
+
|
|
330
|
+
# Build output lines
|
|
331
|
+
lines: list[str] = []
|
|
332
|
+
w = BOX_WIDTH - 1
|
|
333
|
+
|
|
334
|
+
lines.append("+" + "-" * w + "+")
|
|
335
|
+
lines.append(f"| TARGET: {project} - FLUTTER DESIGN SYSTEM".ljust(BOX_WIDTH) + "|")
|
|
336
|
+
lines.append("+" + "-" * w + "+")
|
|
337
|
+
lines.append("|" + " " * BOX_WIDTH + "|")
|
|
338
|
+
|
|
339
|
+
# NEW: Screen Pattern section (from UI-UX Pro Max)
|
|
340
|
+
if screen_pattern.get("name"):
|
|
341
|
+
lines.append(f"| SCREEN PATTERN: {screen_pattern.get('name', '')}".ljust(BOX_WIDTH) + "|")
|
|
342
|
+
if screen_pattern.get("sections"):
|
|
343
|
+
sections_str = screen_pattern.get("sections", "")[:65]
|
|
344
|
+
lines.append(f"| Sections: {sections_str}".ljust(BOX_WIDTH) + "|")
|
|
345
|
+
if screen_pattern.get("cta_placement"):
|
|
346
|
+
lines.append(f"| CTA: {screen_pattern.get('cta_placement', '')}".ljust(BOX_WIDTH) + "|")
|
|
347
|
+
if screen_pattern.get("conversion_optimization"):
|
|
348
|
+
for line in wrap_text(f"Conversion: {screen_pattern.get('conversion_optimization', '')}", "| ", BOX_WIDTH):
|
|
349
|
+
lines.append(line.ljust(BOX_WIDTH) + "|")
|
|
350
|
+
lines.append("|" + " " * BOX_WIDTH + "|")
|
|
351
|
+
|
|
352
|
+
# Architecture Pattern section
|
|
353
|
+
lines.append(f"| ARCHITECTURE: {pattern.get('name', '')}".ljust(BOX_WIDTH) + "|")
|
|
354
|
+
lines.append(f"| Structure: Feature-First / Clean Architecture".ljust(BOX_WIDTH) + "|")
|
|
355
|
+
lines.append(f"| State: {pattern.get('state_management', 'Riverpod')}".ljust(BOX_WIDTH) + "|")
|
|
356
|
+
if pattern.get('recommended_patterns'):
|
|
357
|
+
patterns_str = ", ".join(filter(None, pattern.get('recommended_patterns', [])))[:60]
|
|
358
|
+
lines.append(f"| Patterns: {patterns_str}".ljust(BOX_WIDTH) + "|")
|
|
359
|
+
lines.append("|" + " " * BOX_WIDTH + "|")
|
|
360
|
+
|
|
361
|
+
# Style section
|
|
362
|
+
lines.append(f"| UI STYLE: {style.get('name', '')}".ljust(BOX_WIDTH) + "|")
|
|
363
|
+
if style.get("keywords"):
|
|
364
|
+
for line in wrap_text(f"Keywords: {style.get('keywords', '')}", "| ", BOX_WIDTH):
|
|
365
|
+
lines.append(line.ljust(BOX_WIDTH) + "|")
|
|
366
|
+
if style.get("best_for"):
|
|
367
|
+
for line in wrap_text(f"Best For: {style.get('best_for', '')}", "| ", BOX_WIDTH):
|
|
368
|
+
lines.append(line.ljust(BOX_WIDTH) + "|")
|
|
369
|
+
lines.append("|" + " " * BOX_WIDTH + "|")
|
|
370
|
+
|
|
371
|
+
# Colors section
|
|
372
|
+
lines.append("| COLOR PALETTE:".ljust(BOX_WIDTH) + "|")
|
|
373
|
+
lines.append(f"| Primary: {colors.get('primary', '')}".ljust(BOX_WIDTH) + "|")
|
|
374
|
+
lines.append(f"| Secondary: {colors.get('secondary', '')}".ljust(BOX_WIDTH) + "|")
|
|
375
|
+
lines.append(f"| CTA: {colors.get('cta', '')}".ljust(BOX_WIDTH) + "|")
|
|
376
|
+
lines.append(f"| Background: {colors.get('background', '')}".ljust(BOX_WIDTH) + "|")
|
|
377
|
+
lines.append(f"| Surface: {colors.get('surface', '')}".ljust(BOX_WIDTH) + "|")
|
|
378
|
+
lines.append(f"| Text: {colors.get('text', '')}".ljust(BOX_WIDTH) + "|")
|
|
379
|
+
if colors.get("notes"):
|
|
380
|
+
for line in wrap_text(f"Notes: {colors.get('notes', '')}", "| ", BOX_WIDTH):
|
|
381
|
+
lines.append(line.ljust(BOX_WIDTH) + "|")
|
|
382
|
+
if colors.get("strategy"):
|
|
383
|
+
for line in wrap_text(f"Strategy: {colors.get('strategy', '')}", "| ", BOX_WIDTH):
|
|
384
|
+
lines.append(line.ljust(BOX_WIDTH) + "|")
|
|
385
|
+
lines.append("|" + " " * BOX_WIDTH + "|")
|
|
386
|
+
|
|
387
|
+
# Typography section
|
|
388
|
+
lines.append(f"| TYPOGRAPHY: {typography.get('heading', '')} / {typography.get('body', '')}".ljust(BOX_WIDTH) + "|")
|
|
389
|
+
if typography.get("mood"):
|
|
390
|
+
for line in wrap_text(f"Mood: {typography.get('mood', '')}", "| ", BOX_WIDTH):
|
|
391
|
+
lines.append(line.ljust(BOX_WIDTH) + "|")
|
|
392
|
+
if typography.get("best_for"):
|
|
393
|
+
for line in wrap_text(f"Best For: {typography.get('best_for', '')}", "| ", BOX_WIDTH):
|
|
394
|
+
lines.append(line.ljust(BOX_WIDTH) + "|")
|
|
395
|
+
lines.append("|" + " " * BOX_WIDTH + "|")
|
|
396
|
+
|
|
397
|
+
# Key Effects section
|
|
398
|
+
if effects:
|
|
399
|
+
lines.append("| KEY EFFECTS:".ljust(BOX_WIDTH) + "|")
|
|
400
|
+
for line in wrap_text(effects, "| ", BOX_WIDTH):
|
|
401
|
+
lines.append(line.ljust(BOX_WIDTH) + "|")
|
|
402
|
+
lines.append("|" + " " * BOX_WIDTH + "|")
|
|
403
|
+
|
|
404
|
+
# NEW: Conversion Focus section
|
|
405
|
+
if conversion_focus:
|
|
406
|
+
lines.append("| CONVERSION FOCUS:".ljust(BOX_WIDTH) + "|")
|
|
407
|
+
for line in wrap_text(conversion_focus, "| ", BOX_WIDTH):
|
|
408
|
+
lines.append(line.ljust(BOX_WIDTH) + "|")
|
|
409
|
+
lines.append("|" + " " * BOX_WIDTH + "|")
|
|
410
|
+
|
|
411
|
+
# NEW: Must-Have Features section
|
|
412
|
+
if must_have_features:
|
|
413
|
+
lines.append("| MUST-HAVE FEATURES:".ljust(BOX_WIDTH) + "|")
|
|
414
|
+
for feature in must_have_features:
|
|
415
|
+
lines.append(f"| ✓ {feature}".ljust(BOX_WIDTH) + "|")
|
|
416
|
+
lines.append("|" + " " * BOX_WIDTH + "|")
|
|
417
|
+
|
|
418
|
+
# Anti-patterns section
|
|
419
|
+
if anti_patterns:
|
|
420
|
+
lines.append("| AVOID (Anti-patterns):".ljust(BOX_WIDTH) + "|")
|
|
421
|
+
for line in wrap_text(anti_patterns, "| ", BOX_WIDTH):
|
|
422
|
+
lines.append(line.ljust(BOX_WIDTH) + "|")
|
|
423
|
+
lines.append("|" + " " * BOX_WIDTH + "|")
|
|
424
|
+
|
|
425
|
+
# Flutter-specific checklist
|
|
426
|
+
lines.append("| PRE-DELIVERY CHECKLIST:".ljust(BOX_WIDTH) + "|")
|
|
427
|
+
checklist_items = [
|
|
428
|
+
"[ ] const constructors for immutable widgets",
|
|
429
|
+
"[ ] Keys for lists and animated widgets",
|
|
430
|
+
"[ ] Proper widget decomposition (no God widgets)",
|
|
431
|
+
"[ ] Responsive: 375px, 768px, 1024px breakpoints",
|
|
432
|
+
"[ ] Accessibility: Semantics labels, touch targets >= 48px",
|
|
433
|
+
"[ ] Performance: ListView.builder for long lists",
|
|
434
|
+
"[ ] Error handling: proper error/loading states"
|
|
435
|
+
]
|
|
436
|
+
for item in checklist_items:
|
|
437
|
+
lines.append(f"| {item}".ljust(BOX_WIDTH) + "|")
|
|
438
|
+
lines.append("|" + " " * BOX_WIDTH + "|")
|
|
439
|
+
|
|
440
|
+
lines.append("+" + "-" * w + "+")
|
|
441
|
+
|
|
442
|
+
return "\n".join(lines)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def format_markdown(design_system: dict[str, Any]) -> str:
|
|
446
|
+
"""Format design system as markdown with screen pattern intelligence."""
|
|
447
|
+
project = design_system.get("project_name", "PROJECT")
|
|
448
|
+
pattern = design_system.get("pattern", {})
|
|
449
|
+
screen_pattern = design_system.get("screen_pattern", {})
|
|
450
|
+
style = design_system.get("style", {})
|
|
451
|
+
colors = design_system.get("colors", {})
|
|
452
|
+
typography = design_system.get("typography", {})
|
|
453
|
+
effects = design_system.get("key_effects", "")
|
|
454
|
+
anti_patterns = design_system.get("anti_patterns", "")
|
|
455
|
+
conversion_focus = design_system.get("conversion_focus", "")
|
|
456
|
+
must_have_features = design_system.get("must_have_features", [])
|
|
457
|
+
|
|
458
|
+
lines: list[str] = []
|
|
459
|
+
lines.append(f"## Flutter Design System: {project}")
|
|
460
|
+
lines.append("")
|
|
461
|
+
|
|
462
|
+
# NEW: Screen Pattern section (from UI-UX Pro Max)
|
|
463
|
+
if screen_pattern.get("name"):
|
|
464
|
+
lines.append("### Screen Pattern")
|
|
465
|
+
lines.append(f"- **Pattern:** {screen_pattern.get('name', '')}")
|
|
466
|
+
if screen_pattern.get("sections"):
|
|
467
|
+
lines.append(f"- **Sections:** {screen_pattern.get('sections', '')}")
|
|
468
|
+
if screen_pattern.get("cta_placement"):
|
|
469
|
+
lines.append(f"- **CTA Placement:** {screen_pattern.get('cta_placement', '')}")
|
|
470
|
+
if screen_pattern.get("conversion_optimization"):
|
|
471
|
+
lines.append(f"- **Conversion:** {screen_pattern.get('conversion_optimization', '')}")
|
|
472
|
+
lines.append("")
|
|
473
|
+
|
|
474
|
+
# Architecture section
|
|
475
|
+
lines.append("### Architecture")
|
|
476
|
+
lines.append(f"- **Pattern:** {pattern.get('name', '')}")
|
|
477
|
+
lines.append(f"- **Structure:** Feature-First / Clean Architecture")
|
|
478
|
+
lines.append(f"- **State Management:** {pattern.get('state_management', 'Riverpod')}")
|
|
479
|
+
if pattern.get('recommended_patterns'):
|
|
480
|
+
patterns_str = ", ".join(filter(None, pattern.get('recommended_patterns', [])))
|
|
481
|
+
lines.append(f"- **Recommended Patterns:** {patterns_str}")
|
|
482
|
+
lines.append("")
|
|
483
|
+
|
|
484
|
+
# Style section
|
|
485
|
+
lines.append("### UI Style")
|
|
486
|
+
lines.append(f"- **Name:** {style.get('name', '')}")
|
|
487
|
+
if style.get('keywords'):
|
|
488
|
+
lines.append(f"- **Keywords:** {style.get('keywords', '')}")
|
|
489
|
+
if style.get('best_for'):
|
|
490
|
+
lines.append(f"- **Best For:** {style.get('best_for', '')}")
|
|
491
|
+
if style.get('do_not_use'):
|
|
492
|
+
lines.append(f"- **Do Not Use For:** {style.get('do_not_use', '')}")
|
|
493
|
+
lines.append("")
|
|
494
|
+
|
|
495
|
+
# Colors section
|
|
496
|
+
lines.append("### Color Palette")
|
|
497
|
+
lines.append(f"| Role | Hex | Flutter |")
|
|
498
|
+
lines.append(f"|------|-----|---------|")
|
|
499
|
+
lines.append(f"| Primary | `{colors.get('primary', '')}` | `Color(0xFF{colors.get('primary', '#2563EB')[1:]})` |")
|
|
500
|
+
lines.append(f"| Secondary | `{colors.get('secondary', '')}` | `Color(0xFF{colors.get('secondary', '#3B82F6')[1:]})` |")
|
|
501
|
+
lines.append(f"| CTA | `{colors.get('cta', '')}` | `Color(0xFF{colors.get('cta', '#F97316')[1:]})` |")
|
|
502
|
+
lines.append(f"| Background | `{colors.get('background', '')}` | `Color(0xFF{colors.get('background', '#FFFFFF')[1:]})` |")
|
|
503
|
+
lines.append(f"| Surface | `{colors.get('surface', '')}` | `Color(0xFF{colors.get('surface', '#F8FAFC')[1:]})` |")
|
|
504
|
+
lines.append(f"| Text | `{colors.get('text', '')}` | `Color(0xFF{colors.get('text', '#1E293B')[1:]})` |")
|
|
505
|
+
if colors.get("notes"):
|
|
506
|
+
lines.append(f"\n*Notes: {colors.get('notes', '')}*")
|
|
507
|
+
if colors.get("strategy"):
|
|
508
|
+
lines.append(f"\n*Color Strategy: {colors.get('strategy', '')}*")
|
|
509
|
+
lines.append("")
|
|
510
|
+
|
|
511
|
+
# Typography section
|
|
512
|
+
lines.append("### Typography")
|
|
513
|
+
lines.append(f"- **Heading:** {typography.get('heading', '')}")
|
|
514
|
+
lines.append(f"- **Body:** {typography.get('body', '')}")
|
|
515
|
+
if typography.get("mood"):
|
|
516
|
+
lines.append(f"- **Mood:** {typography.get('mood', '')}")
|
|
517
|
+
if typography.get("best_for"):
|
|
518
|
+
lines.append(f"- **Best For:** {typography.get('best_for', '')}")
|
|
519
|
+
if typography.get("google_fonts_url"):
|
|
520
|
+
lines.append(f"- **Google Fonts:** {typography.get('google_fonts_url', '')}")
|
|
521
|
+
lines.append("")
|
|
522
|
+
|
|
523
|
+
# Key Effects section
|
|
524
|
+
if effects:
|
|
525
|
+
lines.append("### Key Effects")
|
|
526
|
+
lines.append(f"{effects}")
|
|
527
|
+
lines.append("")
|
|
528
|
+
|
|
529
|
+
# NEW: Conversion Focus section
|
|
530
|
+
if conversion_focus:
|
|
531
|
+
lines.append("### Conversion Focus")
|
|
532
|
+
lines.append(f"{conversion_focus}")
|
|
533
|
+
lines.append("")
|
|
534
|
+
|
|
535
|
+
# NEW: Must-Have Features section
|
|
536
|
+
if must_have_features:
|
|
537
|
+
lines.append("### Must-Have Features")
|
|
538
|
+
for feature in must_have_features:
|
|
539
|
+
lines.append(f"- ✓ {feature}")
|
|
540
|
+
lines.append("")
|
|
541
|
+
|
|
542
|
+
# Anti-patterns section
|
|
543
|
+
if anti_patterns:
|
|
544
|
+
lines.append("### Avoid (Anti-patterns)")
|
|
545
|
+
newline_bullet = '\n- '
|
|
546
|
+
lines.append(f"- {anti_patterns.replace(' + ', newline_bullet)}")
|
|
547
|
+
lines.append("")
|
|
548
|
+
|
|
549
|
+
# Pre-Delivery Checklist section
|
|
550
|
+
lines.append("### Pre-Delivery Checklist")
|
|
551
|
+
lines.append("- [ ] `const` constructors for immutable widgets")
|
|
552
|
+
lines.append("- [ ] Keys for lists and animated widgets")
|
|
553
|
+
lines.append("- [ ] Proper widget decomposition (no God widgets)")
|
|
554
|
+
lines.append("- [ ] Responsive: 375px, 768px, 1024px breakpoints")
|
|
555
|
+
lines.append("- [ ] Accessibility: Semantics labels, touch targets >= 48px")
|
|
556
|
+
lines.append("- [ ] Performance: ListView.builder for long lists")
|
|
557
|
+
lines.append("- [ ] Error handling: proper error/loading states")
|
|
558
|
+
lines.append("")
|
|
559
|
+
|
|
560
|
+
return "\n".join(lines)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
# ============ PERSISTENCE FUNCTIONS ============
|
|
564
|
+
def persist_design_system(design_system: dict[str, Any], page: str | None = None, output_dir: str | None = None, page_query: str | None = None) -> dict[str, Any]:
|
|
565
|
+
"""
|
|
566
|
+
Persist design system to design-system/<project>/ folder using Master + Overrides pattern.
|
|
567
|
+
"""
|
|
568
|
+
base_dir = Path(output_dir) if output_dir else Path.cwd()
|
|
569
|
+
|
|
570
|
+
# Use project name for project-specific folder
|
|
571
|
+
project_name = design_system.get("project_name", "default")
|
|
572
|
+
project_slug = project_name.lower().replace(' ', '-')
|
|
573
|
+
|
|
574
|
+
design_system_dir = base_dir / "design-system" / project_slug
|
|
575
|
+
pages_dir = design_system_dir / "pages"
|
|
576
|
+
|
|
577
|
+
created_files: list[str] = []
|
|
578
|
+
|
|
579
|
+
# Create directories
|
|
580
|
+
design_system_dir.mkdir(parents=True, exist_ok=True)
|
|
581
|
+
pages_dir.mkdir(parents=True, exist_ok=True)
|
|
582
|
+
|
|
583
|
+
master_file = design_system_dir / "MASTER.md"
|
|
584
|
+
|
|
585
|
+
# Generate and write MASTER.md
|
|
586
|
+
master_content = format_master_md(design_system)
|
|
587
|
+
with open(master_file, 'w', encoding='utf-8') as f:
|
|
588
|
+
f.write(master_content)
|
|
589
|
+
created_files.append(str(master_file))
|
|
590
|
+
|
|
591
|
+
# If page is specified, create page override file
|
|
592
|
+
if page:
|
|
593
|
+
page_file = pages_dir / f"{page.lower().replace(' ', '-')}.md"
|
|
594
|
+
page_content = format_page_override_md(design_system, page, page_query)
|
|
595
|
+
with open(page_file, 'w', encoding='utf-8') as f:
|
|
596
|
+
f.write(page_content)
|
|
597
|
+
created_files.append(str(page_file))
|
|
598
|
+
|
|
599
|
+
return {
|
|
600
|
+
"status": "success",
|
|
601
|
+
"design_system_dir": str(design_system_dir),
|
|
602
|
+
"created_files": created_files
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def format_master_md(design_system: dict[str, Any]) -> str:
|
|
607
|
+
"""Format design system as MASTER.md with hierarchical override logic."""
|
|
608
|
+
project = design_system.get("project_name", "PROJECT")
|
|
609
|
+
pattern = design_system.get("pattern", {})
|
|
610
|
+
style = design_system.get("style", {})
|
|
611
|
+
colors = design_system.get("colors", {})
|
|
612
|
+
typography = design_system.get("typography", {})
|
|
613
|
+
effects = design_system.get("key_effects", "")
|
|
614
|
+
anti_patterns = design_system.get("anti_patterns", "")
|
|
615
|
+
|
|
616
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
617
|
+
|
|
618
|
+
lines: list[str] = []
|
|
619
|
+
|
|
620
|
+
# Logic header
|
|
621
|
+
lines.append("# Flutter Design System Master File")
|
|
622
|
+
lines.append("")
|
|
623
|
+
lines.append("> **LOGIC:** When building a specific screen, first check `design-system/pages/[screen-name].md`.")
|
|
624
|
+
lines.append("> If that file exists, its rules **override** this Master file.")
|
|
625
|
+
lines.append("> If not, strictly follow the rules below.")
|
|
626
|
+
lines.append("")
|
|
627
|
+
lines.append("---")
|
|
628
|
+
lines.append("")
|
|
629
|
+
lines.append(f"**Project:** {project}")
|
|
630
|
+
lines.append(f"**Generated:** {timestamp}")
|
|
631
|
+
lines.append(f"**Category:** {design_system.get('category', 'General')}")
|
|
632
|
+
lines.append("")
|
|
633
|
+
lines.append("---")
|
|
634
|
+
lines.append("")
|
|
635
|
+
|
|
636
|
+
# Architecture section
|
|
637
|
+
lines.append("## Architecture")
|
|
638
|
+
lines.append("")
|
|
639
|
+
lines.append(f"- **Pattern:** {pattern.get('name', 'Clean Architecture')}")
|
|
640
|
+
lines.append(f"- **Structure:** Feature-First")
|
|
641
|
+
lines.append(f"- **State Management:** {pattern.get('state_management', 'Riverpod')}")
|
|
642
|
+
lines.append("")
|
|
643
|
+
lines.append("### Folder Structure")
|
|
644
|
+
lines.append("```")
|
|
645
|
+
lines.append("lib/")
|
|
646
|
+
lines.append("├── core/ # Shared utilities, constants, themes")
|
|
647
|
+
lines.append("│ ├── theme/")
|
|
648
|
+
lines.append("│ ├── utils/")
|
|
649
|
+
lines.append("│ └── widgets/ # Reusable widgets")
|
|
650
|
+
lines.append("├── features/")
|
|
651
|
+
lines.append("│ └── [feature_name]/")
|
|
652
|
+
lines.append("│ ├── data/ # Repositories, data sources")
|
|
653
|
+
lines.append("│ ├── domain/ # Entities, use cases")
|
|
654
|
+
lines.append("│ └── presentation/ # Screens, widgets, state")
|
|
655
|
+
lines.append("└── main.dart")
|
|
656
|
+
lines.append("```")
|
|
657
|
+
lines.append("")
|
|
658
|
+
|
|
659
|
+
# Global Rules section
|
|
660
|
+
lines.append("## Global Rules")
|
|
661
|
+
lines.append("")
|
|
662
|
+
|
|
663
|
+
# Color Palette
|
|
664
|
+
lines.append("### Color Palette")
|
|
665
|
+
lines.append("")
|
|
666
|
+
lines.append("| Role | Hex | Flutter Color |")
|
|
667
|
+
lines.append("|------|-----|---------------|")
|
|
668
|
+
primary_hex = colors.get('primary', '#2563EB').lstrip('#')
|
|
669
|
+
secondary_hex = colors.get('secondary', '#3B82F6').lstrip('#')
|
|
670
|
+
cta_hex = colors.get('cta', '#F97316').lstrip('#')
|
|
671
|
+
bg_hex = colors.get('background', '#FFFFFF').lstrip('#')
|
|
672
|
+
surface_hex = colors.get('surface', '#F8FAFC').lstrip('#')
|
|
673
|
+
text_hex = colors.get('text', '#1E293B').lstrip('#')
|
|
674
|
+
|
|
675
|
+
lines.append(f"| Primary | `#{primary_hex}` | `Color(0xFF{primary_hex.upper()})` |")
|
|
676
|
+
lines.append(f"| Secondary | `#{secondary_hex}` | `Color(0xFF{secondary_hex.upper()})` |")
|
|
677
|
+
lines.append(f"| CTA/Accent | `#{cta_hex}` | `Color(0xFF{cta_hex.upper()})` |")
|
|
678
|
+
lines.append(f"| Background | `#{bg_hex}` | `Color(0xFF{bg_hex.upper()})` |")
|
|
679
|
+
lines.append(f"| Surface | `#{surface_hex}` | `Color(0xFF{surface_hex.upper()})` |")
|
|
680
|
+
lines.append(f"| Text | `#{text_hex}` | `Color(0xFF{text_hex.upper()})` |")
|
|
681
|
+
lines.append("")
|
|
682
|
+
|
|
683
|
+
if colors.get("notes"):
|
|
684
|
+
lines.append(f"**Color Notes:** {colors.get('notes', '')}")
|
|
685
|
+
lines.append("")
|
|
686
|
+
|
|
687
|
+
# Typography
|
|
688
|
+
lines.append("### Typography")
|
|
689
|
+
lines.append("")
|
|
690
|
+
lines.append(f"- **Heading Font:** {typography.get('heading', 'Inter')}")
|
|
691
|
+
lines.append(f"- **Body Font:** {typography.get('body', 'Inter')}")
|
|
692
|
+
if typography.get("mood"):
|
|
693
|
+
lines.append(f"- **Mood:** {typography.get('mood', '')}")
|
|
694
|
+
if typography.get("google_fonts_url"):
|
|
695
|
+
lines.append(f"- **Google Fonts:** [{typography.get('heading', '')} + {typography.get('body', '')}]({typography.get('google_fonts_url', '')})")
|
|
696
|
+
lines.append("")
|
|
697
|
+
|
|
698
|
+
lines.append("**Font Sizes (Scale):**")
|
|
699
|
+
lines.append("```dart")
|
|
700
|
+
lines.append("// Display")
|
|
701
|
+
lines.append("displayLarge: 57, displayMedium: 45, displaySmall: 36")
|
|
702
|
+
lines.append("// Headline")
|
|
703
|
+
lines.append("headlineLarge: 32, headlineMedium: 28, headlineSmall: 24")
|
|
704
|
+
lines.append("// Title")
|
|
705
|
+
lines.append("titleLarge: 22, titleMedium: 16, titleSmall: 14")
|
|
706
|
+
lines.append("// Body")
|
|
707
|
+
lines.append("bodyLarge: 16, bodyMedium: 14, bodySmall: 12")
|
|
708
|
+
lines.append("// Label")
|
|
709
|
+
lines.append("labelLarge: 14, labelMedium: 12, labelSmall: 11")
|
|
710
|
+
lines.append("```")
|
|
711
|
+
lines.append("")
|
|
712
|
+
|
|
713
|
+
# Spacing
|
|
714
|
+
lines.append("### Spacing")
|
|
715
|
+
lines.append("")
|
|
716
|
+
lines.append("| Token | Value | Usage |")
|
|
717
|
+
lines.append("|-------|-------|-------|")
|
|
718
|
+
lines.append("| `xs` | `4` | Tight gaps |")
|
|
719
|
+
lines.append("| `sm` | `8` | Icon gaps, inline spacing |")
|
|
720
|
+
lines.append("| `md` | `16` | Standard padding |")
|
|
721
|
+
lines.append("| `lg` | `24` | Section padding |")
|
|
722
|
+
lines.append("| `xl` | `32` | Large gaps |")
|
|
723
|
+
lines.append("| `xxl` | `48` | Section margins |")
|
|
724
|
+
lines.append("")
|
|
725
|
+
|
|
726
|
+
# Border Radius
|
|
727
|
+
lines.append("### Border Radius")
|
|
728
|
+
lines.append("")
|
|
729
|
+
lines.append("| Token | Value | Usage |")
|
|
730
|
+
lines.append("|-------|-------|-------|")
|
|
731
|
+
lines.append("| `xs` | `4` | Small chips, badges |")
|
|
732
|
+
lines.append("| `sm` | `8` | Buttons, inputs |")
|
|
733
|
+
lines.append("| `md` | `12` | Cards |")
|
|
734
|
+
lines.append("| `lg` | `16` | Modals, bottom sheets |")
|
|
735
|
+
lines.append("| `xl` | `24` | Large containers |")
|
|
736
|
+
lines.append("| `full` | `9999` | Pills, circular |")
|
|
737
|
+
lines.append("")
|
|
738
|
+
|
|
739
|
+
# Style section
|
|
740
|
+
lines.append("---")
|
|
741
|
+
lines.append("")
|
|
742
|
+
lines.append("## Style Guidelines")
|
|
743
|
+
lines.append("")
|
|
744
|
+
lines.append(f"**Style:** {style.get('name', 'Minimalism')}")
|
|
745
|
+
lines.append("")
|
|
746
|
+
if style.get("keywords"):
|
|
747
|
+
lines.append(f"**Keywords:** {style.get('keywords', '')}")
|
|
748
|
+
lines.append("")
|
|
749
|
+
if style.get("best_for"):
|
|
750
|
+
lines.append(f"**Best For:** {style.get('best_for', '')}")
|
|
751
|
+
lines.append("")
|
|
752
|
+
if effects:
|
|
753
|
+
lines.append(f"**Key Effects:** {effects}")
|
|
754
|
+
lines.append("")
|
|
755
|
+
|
|
756
|
+
# Anti-Patterns section
|
|
757
|
+
lines.append("---")
|
|
758
|
+
lines.append("")
|
|
759
|
+
lines.append("## Anti-Patterns (Do NOT Use)")
|
|
760
|
+
lines.append("")
|
|
761
|
+
if anti_patterns:
|
|
762
|
+
anti_list = [a.strip() for a in anti_patterns.split("+")]
|
|
763
|
+
for anti in anti_list:
|
|
764
|
+
if anti:
|
|
765
|
+
lines.append(f"- ❌ {anti}")
|
|
766
|
+
lines.append("")
|
|
767
|
+
lines.append("### Flutter-Specific Forbidden Patterns")
|
|
768
|
+
lines.append("")
|
|
769
|
+
lines.append("- ❌ **God Widgets** — Widgets > 200 lines, split into smaller components")
|
|
770
|
+
lines.append("- ❌ **Business logic in UI** — Move to providers/blocs/use cases")
|
|
771
|
+
lines.append("- ❌ **Missing const** — Always use `const` for immutable widgets")
|
|
772
|
+
lines.append("- ❌ **setState abuse** — Use proper state management for complex state")
|
|
773
|
+
lines.append("- ❌ **Hardcoded strings/colors** — Use theme and localization")
|
|
774
|
+
lines.append("- ❌ **Missing error states** — Always handle loading/error/empty states")
|
|
775
|
+
lines.append("")
|
|
776
|
+
|
|
777
|
+
# Pre-Delivery Checklist
|
|
778
|
+
lines.append("---")
|
|
779
|
+
lines.append("")
|
|
780
|
+
lines.append("## Pre-Delivery Checklist")
|
|
781
|
+
lines.append("")
|
|
782
|
+
lines.append("Before delivering any Flutter code, verify:")
|
|
783
|
+
lines.append("")
|
|
784
|
+
lines.append("- [ ] `const` constructors used for immutable widgets")
|
|
785
|
+
lines.append("- [ ] Proper Keys for lists and animated widgets")
|
|
786
|
+
lines.append("- [ ] Widget decomposition (no files > 300 lines)")
|
|
787
|
+
lines.append("- [ ] Responsive: works on 375px, 768px, 1024px")
|
|
788
|
+
lines.append("- [ ] Accessibility: Semantics widgets, touch targets >= 48x48")
|
|
789
|
+
lines.append("- [ ] Performance: ListView.builder for long lists")
|
|
790
|
+
lines.append("- [ ] State management: no setState for complex state")
|
|
791
|
+
lines.append("- [ ] Theme: colors and text styles from ThemeData")
|
|
792
|
+
lines.append("- [ ] Error handling: loading, error, empty states")
|
|
793
|
+
lines.append("- [ ] Null safety: no force unwrapping (!)")
|
|
794
|
+
lines.append("")
|
|
795
|
+
|
|
796
|
+
return "\n".join(lines)
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def format_page_override_md(design_system: dict[str, Any], page_name: str, page_query: str | None = None) -> str:
|
|
800
|
+
"""Format a page-specific override file."""
|
|
801
|
+
project = design_system.get("project_name", "PROJECT")
|
|
802
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
803
|
+
page_title = page_name.replace("-", " ").replace("_", " ").title()
|
|
804
|
+
|
|
805
|
+
# Detect page type and generate intelligent overrides
|
|
806
|
+
page_overrides = _generate_intelligent_overrides(page_name, page_query, design_system)
|
|
807
|
+
|
|
808
|
+
lines: list[str] = []
|
|
809
|
+
|
|
810
|
+
lines.append(f"# {page_title} Screen Overrides")
|
|
811
|
+
lines.append("")
|
|
812
|
+
lines.append(f"> **PROJECT:** {project}")
|
|
813
|
+
lines.append(f"> **Generated:** {timestamp}")
|
|
814
|
+
lines.append(f"> **Screen Type:** {page_overrides.get('page_type', 'General')}")
|
|
815
|
+
lines.append("")
|
|
816
|
+
lines.append("> ⚠️ **IMPORTANT:** Rules in this file **override** the Master file (`design-system/MASTER.md`).")
|
|
817
|
+
lines.append("> Only deviations from the Master are documented here. For all other rules, refer to the Master.")
|
|
818
|
+
lines.append("")
|
|
819
|
+
lines.append("---")
|
|
820
|
+
lines.append("")
|
|
821
|
+
|
|
822
|
+
# Screen-specific rules
|
|
823
|
+
lines.append("## Screen-Specific Rules")
|
|
824
|
+
lines.append("")
|
|
825
|
+
|
|
826
|
+
# Layout Overrides
|
|
827
|
+
lines.append("### Layout Overrides")
|
|
828
|
+
lines.append("")
|
|
829
|
+
layout = page_overrides.get("layout", {})
|
|
830
|
+
if layout:
|
|
831
|
+
for key, value in layout.items():
|
|
832
|
+
lines.append(f"- **{key}:** {value}")
|
|
833
|
+
else:
|
|
834
|
+
lines.append("- No overrides — use Master layout")
|
|
835
|
+
lines.append("")
|
|
836
|
+
|
|
837
|
+
# Widget Recommendations
|
|
838
|
+
lines.append("### Recommended Widgets")
|
|
839
|
+
lines.append("")
|
|
840
|
+
widgets = page_overrides.get("widgets", [])
|
|
841
|
+
if widgets:
|
|
842
|
+
for widget in widgets:
|
|
843
|
+
lines.append(f"- {widget}")
|
|
844
|
+
else:
|
|
845
|
+
lines.append("- Use standard widgets from Master")
|
|
846
|
+
lines.append("")
|
|
847
|
+
|
|
848
|
+
# State Management
|
|
849
|
+
lines.append("### State Management")
|
|
850
|
+
lines.append("")
|
|
851
|
+
state = page_overrides.get("state", {})
|
|
852
|
+
if state:
|
|
853
|
+
for key, value in state.items():
|
|
854
|
+
lines.append(f"- **{key}:** {value}")
|
|
855
|
+
else:
|
|
856
|
+
lines.append("- No overrides — use Master state management")
|
|
857
|
+
lines.append("")
|
|
858
|
+
|
|
859
|
+
# Recommendations
|
|
860
|
+
lines.append("---")
|
|
861
|
+
lines.append("")
|
|
862
|
+
lines.append("## Recommendations")
|
|
863
|
+
lines.append("")
|
|
864
|
+
recommendations = page_overrides.get("recommendations", [])
|
|
865
|
+
if recommendations:
|
|
866
|
+
for rec in recommendations:
|
|
867
|
+
lines.append(f"- {rec}")
|
|
868
|
+
lines.append("")
|
|
869
|
+
|
|
870
|
+
return "\n".join(lines)
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def _generate_intelligent_overrides(page_name: str, page_query: str | None, design_system: dict[str, Any]) -> dict[str, Any]:
|
|
874
|
+
"""
|
|
875
|
+
Generate intelligent overrides based on page type using layered search.
|
|
876
|
+
Enhanced with UX guidelines and landing pattern intelligence from UI-UX Pro Max.
|
|
877
|
+
"""
|
|
878
|
+
from core import search
|
|
879
|
+
|
|
880
|
+
page_lower = page_name.lower()
|
|
881
|
+
query_lower = (page_query or "").lower()
|
|
882
|
+
combined_context = f"{page_lower} {query_lower}"
|
|
883
|
+
|
|
884
|
+
# Multi-domain search for page-specific guidance
|
|
885
|
+
pattern_search = search(combined_context, "pattern", max_results=2)
|
|
886
|
+
widget_search = search(combined_context, "widget", max_results=3)
|
|
887
|
+
ux_search = search(combined_context, "ux", max_results=3)
|
|
888
|
+
landing_search = search(combined_context, "landing", max_results=1)
|
|
889
|
+
|
|
890
|
+
pattern_results = pattern_search.get("results", [])
|
|
891
|
+
widget_results = widget_search.get("results", [])
|
|
892
|
+
ux_results = ux_search.get("results", [])
|
|
893
|
+
landing_results = landing_search.get("results", [])
|
|
894
|
+
|
|
895
|
+
# Detect page type
|
|
896
|
+
page_type = _detect_page_type(combined_context)
|
|
897
|
+
|
|
898
|
+
# Build overrides
|
|
899
|
+
layout: dict[str, str] = {}
|
|
900
|
+
widgets: list[str] = []
|
|
901
|
+
state: dict[str, str] = {}
|
|
902
|
+
recommendations: list[str] = []
|
|
903
|
+
ux_guidelines: list[str] = []
|
|
904
|
+
|
|
905
|
+
# Extract widget recommendations
|
|
906
|
+
for widget in widget_results:
|
|
907
|
+
widget_name = widget.get("Widget Name", "")
|
|
908
|
+
if widget_name:
|
|
909
|
+
widgets.append(f"`{widget_name}` - {widget.get('Description', '')[:60]}...")
|
|
910
|
+
|
|
911
|
+
# Extract pattern recommendations
|
|
912
|
+
for pattern in pattern_results:
|
|
913
|
+
pattern_name = pattern.get("pattern_name", "")
|
|
914
|
+
if pattern_name:
|
|
915
|
+
recommendations.append(f"Consider `{pattern_name}` pattern")
|
|
916
|
+
|
|
917
|
+
# NEW: Extract UX guidelines as recommendations
|
|
918
|
+
for ux in ux_results:
|
|
919
|
+
category = ux.get("Category", "")
|
|
920
|
+
do_text = ux.get("Do", "")
|
|
921
|
+
dont_text = ux.get("Don't", "")
|
|
922
|
+
if do_text:
|
|
923
|
+
ux_guidelines.append(f"✓ {category}: {do_text[:80]}...")
|
|
924
|
+
if dont_text:
|
|
925
|
+
ux_guidelines.append(f"✗ Avoid: {dont_text[:80]}...")
|
|
926
|
+
|
|
927
|
+
# NEW: Extract landing pattern info for screen structure
|
|
928
|
+
if landing_results:
|
|
929
|
+
landing = landing_results[0]
|
|
930
|
+
sections = landing.get("Section Order", "")
|
|
931
|
+
cta_placement = landing.get("Primary CTA Placement", "")
|
|
932
|
+
conversion = landing.get("Conversion Optimization", "")
|
|
933
|
+
|
|
934
|
+
if sections:
|
|
935
|
+
layout["Sections"] = sections[:80]
|
|
936
|
+
if cta_placement:
|
|
937
|
+
layout["CTA Placement"] = cta_placement
|
|
938
|
+
if conversion:
|
|
939
|
+
recommendations.append(f"Conversion: {conversion[:80]}...")
|
|
940
|
+
|
|
941
|
+
# Add page-type specific defaults
|
|
942
|
+
if "dashboard" in page_lower or "analytics" in page_lower:
|
|
943
|
+
layout["Grid"] = "Use GridView or Wrap for widget cards"
|
|
944
|
+
layout["Scrolling"] = "CustomScrollView with slivers"
|
|
945
|
+
state["Refresh"] = "Pull-to-refresh with RefreshIndicator"
|
|
946
|
+
state["Real-time"] = "Consider StreamBuilder for live data"
|
|
947
|
+
elif "list" in page_lower or "search" in page_lower:
|
|
948
|
+
layout["List"] = "ListView.builder for performance"
|
|
949
|
+
layout["Pagination"] = "Infinite scroll with lazy loading"
|
|
950
|
+
state["Search"] = "Debounced search with 300ms delay"
|
|
951
|
+
state["Empty State"] = "Show helpful empty state with action"
|
|
952
|
+
elif "form" in page_lower or "profile" in page_lower or "settings" in page_lower:
|
|
953
|
+
layout["Form"] = "Single column, max 600px width"
|
|
954
|
+
state["Validation"] = "Form validation with FormField"
|
|
955
|
+
state["Autosave"] = "Consider debounced autosave for settings"
|
|
956
|
+
elif "login" in page_lower or "auth" in page_lower:
|
|
957
|
+
layout["Layout"] = "Centered, max 400px width"
|
|
958
|
+
state["Auth"] = "Handle loading, error, success states"
|
|
959
|
+
state["Biometric"] = "Consider biometric authentication"
|
|
960
|
+
elif "checkout" in page_lower or "payment" in page_lower:
|
|
961
|
+
layout["Layout"] = "Single column, focused flow"
|
|
962
|
+
state["Progress"] = "Show step indicator for multi-step"
|
|
963
|
+
state["Validation"] = "Real-time validation for payment fields"
|
|
964
|
+
elif "chat" in page_lower or "message" in page_lower:
|
|
965
|
+
layout["List"] = "Reversed ListView for chat bubbles"
|
|
966
|
+
state["Real-time"] = "WebSocket or Firebase for live updates"
|
|
967
|
+
state["Typing"] = "Show typing indicator"
|
|
968
|
+
|
|
969
|
+
# Add UX guidelines to recommendations
|
|
970
|
+
recommendations.extend(ux_guidelines[:3]) # Limit to top 3
|
|
971
|
+
|
|
972
|
+
if not recommendations:
|
|
973
|
+
recommendations = [
|
|
974
|
+
"Refer to MASTER.md for all design rules",
|
|
975
|
+
"Add specific overrides as needed for this screen"
|
|
976
|
+
]
|
|
977
|
+
|
|
978
|
+
return {
|
|
979
|
+
"page_type": page_type,
|
|
980
|
+
"layout": layout,
|
|
981
|
+
"widgets": widgets,
|
|
982
|
+
"state": state,
|
|
983
|
+
"recommendations": recommendations
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
def _detect_page_type(context: str) -> str:
|
|
988
|
+
"""Detect page type from context."""
|
|
989
|
+
context_lower = context.lower()
|
|
990
|
+
|
|
991
|
+
page_patterns: list[tuple[list[str], str]] = [
|
|
992
|
+
(["dashboard", "admin", "analytics", "metrics", "stats", "overview"], "Dashboard"),
|
|
993
|
+
(["list", "search", "browse", "filter", "catalog"], "List / Search"),
|
|
994
|
+
(["detail", "product", "item", "view"], "Detail View"),
|
|
995
|
+
(["form", "edit", "create", "add", "new"], "Form / Input"),
|
|
996
|
+
(["profile", "settings", "account", "preferences"], "Settings / Profile"),
|
|
997
|
+
(["login", "signin", "signup", "register", "auth"], "Authentication"),
|
|
998
|
+
(["home", "landing", "welcome"], "Home / Landing"),
|
|
999
|
+
(["chat", "message", "conversation"], "Chat / Messaging"),
|
|
1000
|
+
(["checkout", "payment", "cart", "order"], "Checkout / Payment"),
|
|
1001
|
+
]
|
|
1002
|
+
|
|
1003
|
+
for keywords, page_type in page_patterns:
|
|
1004
|
+
if any(kw in context_lower for kw in keywords):
|
|
1005
|
+
return page_type
|
|
1006
|
+
|
|
1007
|
+
return "General"
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
# ============ MAIN ENTRY POINT ============
|
|
1011
|
+
def generate_design_system(query: str, project_name: str | None = None, output_format: str = "ascii",
|
|
1012
|
+
persist: bool = False, page: str | None = None, output_dir: str | None = None) -> str:
|
|
1013
|
+
"""
|
|
1014
|
+
Main entry point for design system generation.
|
|
1015
|
+
|
|
1016
|
+
Args:
|
|
1017
|
+
query: Search query (e.g., "fintech banking app", "e-commerce mobile")
|
|
1018
|
+
project_name: Optional project name for output header
|
|
1019
|
+
output_format: "ascii" (default) or "markdown"
|
|
1020
|
+
persist: If True, save design system to design-system/ folder
|
|
1021
|
+
page: Optional page name for page-specific override file
|
|
1022
|
+
output_dir: Optional output directory (defaults to current working directory)
|
|
1023
|
+
|
|
1024
|
+
Returns:
|
|
1025
|
+
Formatted design system string
|
|
1026
|
+
"""
|
|
1027
|
+
generator = DesignSystemGenerator()
|
|
1028
|
+
design_system = generator.generate(query, project_name)
|
|
1029
|
+
|
|
1030
|
+
# Persist to files if requested
|
|
1031
|
+
if persist:
|
|
1032
|
+
persist_design_system(design_system, page, output_dir, query)
|
|
1033
|
+
|
|
1034
|
+
if output_format == "markdown":
|
|
1035
|
+
return format_markdown(design_system)
|
|
1036
|
+
return format_ascii_box(design_system)
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
# ============ CLI SUPPORT ============
|
|
1040
|
+
if __name__ == "__main__":
|
|
1041
|
+
import argparse
|
|
1042
|
+
|
|
1043
|
+
parser = argparse.ArgumentParser(description="Generate Flutter Design System")
|
|
1044
|
+
parser.add_argument("query", help="Search query (e.g., 'fintech banking app')")
|
|
1045
|
+
parser.add_argument("--project-name", "-p", type=str, default=None, help="Project name")
|
|
1046
|
+
parser.add_argument("--format", "-f", choices=["ascii", "markdown"], default="ascii", help="Output format")
|
|
1047
|
+
parser.add_argument("--persist", action="store_true", help="Save to design-system/ folder")
|
|
1048
|
+
parser.add_argument("--page", type=str, default=None, help="Create page-specific override file")
|
|
1049
|
+
parser.add_argument("--output-dir", "-o", type=str, default=None, help="Output directory")
|
|
1050
|
+
|
|
1051
|
+
args = parser.parse_args()
|
|
1052
|
+
|
|
1053
|
+
result = generate_design_system(
|
|
1054
|
+
args.query,
|
|
1055
|
+
args.project_name,
|
|
1056
|
+
args.format,
|
|
1057
|
+
persist=args.persist,
|
|
1058
|
+
page=args.page,
|
|
1059
|
+
output_dir=args.output_dir
|
|
1060
|
+
)
|
|
1061
|
+
print(result)
|
|
1062
|
+
|
|
1063
|
+
if args.persist:
|
|
1064
|
+
project_slug = args.project_name.lower().replace(' ', '-') if args.project_name else "default"
|
|
1065
|
+
print("\n" + "=" * 60)
|
|
1066
|
+
print(f"✅ Design system persisted to design-system/{project_slug}/")
|
|
1067
|
+
print(f" 📄 design-system/{project_slug}/MASTER.md (Global Source of Truth)")
|
|
1068
|
+
if args.page:
|
|
1069
|
+
page_filename = args.page.lower().replace(' ', '-')
|
|
1070
|
+
print(f" 📄 design-system/{project_slug}/pages/{page_filename}.md (Screen Overrides)")
|
|
1071
|
+
print("")
|
|
1072
|
+
print(f"📖 Usage: When building a screen, check design-system/{project_slug}/pages/[screen].md first.")
|
|
1073
|
+
print(f" If exists, its rules override MASTER.md. Otherwise, use MASTER.md.")
|
|
1074
|
+
print("=" * 60)
|