cursorflow 2.4.3__tar.gz → 2.6.0__tar.gz
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.
- {cursorflow-2.4.3 → cursorflow-2.6.0}/PKG-INFO +8 -6
- {cursorflow-2.4.3 → cursorflow-2.6.0}/README.md +7 -5
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/cli.py +11 -19
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/cursorflow.py +3 -13
- cursorflow-2.6.0/cursorflow/core/json_utils.py +53 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/mockup_comparator.py +29 -58
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/rules/cursorflow-usage.mdc +25 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow.egg-info/SOURCES.txt +1 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/docs/user/USAGE_GUIDE.md +198 -0
- cursorflow-2.6.0/examples/mockup_comparison_example.py +195 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/pyproject.toml +2 -2
- cursorflow-2.4.3/examples/mockup_comparison_example.py +0 -316
- {cursorflow-2.4.3 → cursorflow-2.6.0}/LICENSE +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/MANIFEST.in +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/__init__.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/auto_init.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/auto_updater.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/action_validator.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/agent.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/auth_handler.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/browser_controller.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/browser_engine.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/config_validator.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/css_iterator.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/cursor_integration.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/error_context_collector.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/error_correlator.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/event_correlator.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/file_change_monitor.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/hmr_detector.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/log_collector.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/log_monitor.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/persistent_session.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/report_generator.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/core/trace_manager.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/install_cursorflow_rules.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/log_sources/local_file.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/log_sources/ssh_remote.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/post_install.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/rules/__init__.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/rules/cursorflow-installation.mdc +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/cursorflow/updater.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/examples/comprehensive_screenshot_example.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/examples/element_inspection_example.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/examples/element_measurement_example.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/examples/enhanced_screenshot_example.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/examples/hot_reload_css_iteration.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/examples/opensas_example.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/examples/react_example.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/examples/responsive_testing_example.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/examples/v2_comprehensive_demo.py +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/setup.cfg +0 -0
- {cursorflow-2.4.3 → cursorflow-2.6.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cursorflow
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.6.0
|
4
4
|
Summary: 🔥 Complete page intelligence for AI-driven development with Hot Reload Intelligence - captures DOM, network, console, performance, HMR events, and comprehensive page analysis
|
5
5
|
Author-email: GeekWarrior Development <rbush@cooltheory.com>
|
6
6
|
License-Expression: MIT
|
@@ -370,20 +370,22 @@ cursorflow test --base-url http://localhost:3000 --responsive --actions '[
|
|
370
370
|
cursorflow test --base-url http://localhost:3000 --path "/api" --output "api-test-results.json"
|
371
371
|
```
|
372
372
|
|
373
|
-
### **Design Comparison**
|
373
|
+
### **Design Comparison** (Pure Measurement)
|
374
374
|
```bash
|
375
|
-
# Compare mockup to implementation
|
375
|
+
# Compare mockup to implementation - get similarity metrics
|
376
376
|
cursorflow compare-mockup https://mockup.com/design \
|
377
377
|
--base-url http://localhost:3000 \
|
378
|
-
--mockup-actions '[{"navigate": "/"}]' \
|
379
378
|
--implementation-actions '[{"navigate": "/dashboard"}]'
|
379
|
+
# Output: 87.3% similarity, diff images, element measurements
|
380
380
|
|
381
|
-
# CSS
|
381
|
+
# Test CSS variations - observe real rendering
|
382
382
|
cursorflow iterate-mockup https://mockup.com/design \
|
383
383
|
--base-url http://localhost:5173 \
|
384
384
|
--css-improvements '[
|
385
|
-
{"name": "fix
|
385
|
+
{"name": "spacing-fix", "css": ".container { gap: 2rem; }"},
|
386
|
+
{"name": "tighter-spacing", "css": ".container { gap: 1rem; }"}
|
386
387
|
]'
|
388
|
+
# Output: Similarity data for each variation (Cursor decides which to apply)
|
387
389
|
```
|
388
390
|
|
389
391
|
### **Element Analysis & CSS Debugging**
|
@@ -325,20 +325,22 @@ cursorflow test --base-url http://localhost:3000 --responsive --actions '[
|
|
325
325
|
cursorflow test --base-url http://localhost:3000 --path "/api" --output "api-test-results.json"
|
326
326
|
```
|
327
327
|
|
328
|
-
### **Design Comparison**
|
328
|
+
### **Design Comparison** (Pure Measurement)
|
329
329
|
```bash
|
330
|
-
# Compare mockup to implementation
|
330
|
+
# Compare mockup to implementation - get similarity metrics
|
331
331
|
cursorflow compare-mockup https://mockup.com/design \
|
332
332
|
--base-url http://localhost:3000 \
|
333
|
-
--mockup-actions '[{"navigate": "/"}]' \
|
334
333
|
--implementation-actions '[{"navigate": "/dashboard"}]'
|
334
|
+
# Output: 87.3% similarity, diff images, element measurements
|
335
335
|
|
336
|
-
# CSS
|
336
|
+
# Test CSS variations - observe real rendering
|
337
337
|
cursorflow iterate-mockup https://mockup.com/design \
|
338
338
|
--base-url http://localhost:5173 \
|
339
339
|
--css-improvements '[
|
340
|
-
{"name": "fix
|
340
|
+
{"name": "spacing-fix", "css": ".container { gap: 2rem; }"},
|
341
|
+
{"name": "tighter-spacing", "css": ".container { gap: 1rem; }"}
|
341
342
|
]'
|
343
|
+
# Output: Similarity data for each variation (Cursor decides which to apply)
|
342
344
|
```
|
343
345
|
|
344
346
|
### **Element Analysis & CSS Debugging**
|
@@ -428,29 +428,20 @@ def compare_mockup(mockup_url, base_url, mockup_actions, implementation_actions,
|
|
428
428
|
console.print(f"[red]❌ Comparison failed: {results['error']}[/red]")
|
429
429
|
return
|
430
430
|
|
431
|
-
# Display results summary
|
431
|
+
# Display results summary (pure metrics only)
|
432
432
|
summary = results.get('summary', {})
|
433
433
|
console.print(f"✅ Comparison completed: {results.get('comparison_id', 'unknown')}")
|
434
434
|
console.print(f"📊 Average similarity: [bold]{summary.get('average_similarity', 0)}%[/bold]")
|
435
435
|
console.print(f"📱 Viewports tested: {summary.get('viewports_tested', 0)}")
|
436
436
|
|
437
|
-
# Show
|
438
|
-
|
439
|
-
if
|
440
|
-
console.print(f"
|
441
|
-
for i, rec in enumerate(recommendations[:3]): # Show first 3
|
442
|
-
console.print(f" {i+1}. {rec.get('description', 'No description')}")
|
437
|
+
# Show similarity range
|
438
|
+
similarity_range = summary.get('similarity_range', {})
|
439
|
+
if similarity_range:
|
440
|
+
console.print(f"📈 Similarity range: {similarity_range.get('min', 0)}% - {similarity_range.get('max', 0)}%")
|
443
441
|
|
444
|
-
# Save results with
|
445
|
-
|
446
|
-
|
447
|
-
# Handle numpy types
|
448
|
-
if hasattr(obj, 'item'): # numpy scalars (bool_, int_, float_)
|
449
|
-
return obj.item()
|
450
|
-
if hasattr(obj, 'tolist'): # numpy arrays
|
451
|
-
return obj.tolist()
|
452
|
-
return str(obj)
|
453
|
-
json.dump(results, f, indent=2, default=json_serializer)
|
442
|
+
# Save results with safe serialization
|
443
|
+
from .core.json_utils import safe_json_dump
|
444
|
+
safe_json_dump(results, output)
|
454
445
|
|
455
446
|
console.print(f"💾 Full results saved to: [cyan]{output}[/cyan]")
|
456
447
|
console.print(f"📁 Visual diffs stored in: [cyan].cursorflow/artifacts/[/cyan]")
|
@@ -551,9 +542,10 @@ def iterate_mockup(mockup_url, base_url, css_improvements, base_actions, diff_th
|
|
551
542
|
for i, rec in enumerate(recommendations[:3]):
|
552
543
|
console.print(f" {i+1}. {rec.get('description', 'No description')}")
|
553
544
|
|
554
|
-
# Save results
|
545
|
+
# Save results with numpy type handling
|
546
|
+
from cursorflow.core.json_utils import safe_json_serialize
|
555
547
|
with open(output, 'w') as f:
|
556
|
-
json.dump(results, f, indent=2, default=
|
548
|
+
json.dump(results, f, indent=2, default=safe_json_serialize)
|
557
549
|
|
558
550
|
console.print(f"💾 Full results saved to: [cyan]{output}[/cyan]")
|
559
551
|
console.print(f"📁 Iteration progress stored in: [cyan].cursorflow/artifacts/[/cyan]")
|
@@ -575,19 +575,9 @@ class CursorFlow:
|
|
575
575
|
if "error" in raw_results:
|
576
576
|
return raw_results
|
577
577
|
|
578
|
-
#
|
579
|
-
|
580
|
-
|
581
|
-
raw_results=raw_results,
|
582
|
-
session_id=session_id,
|
583
|
-
project_context={
|
584
|
-
"framework": "auto-detected",
|
585
|
-
"base_url": self.base_url,
|
586
|
-
"test_type": "mockup_comparison"
|
587
|
-
}
|
588
|
-
)
|
589
|
-
|
590
|
-
return cursor_results
|
578
|
+
# Return raw measurement data directly (pure observation)
|
579
|
+
# No interpretation, no analysis - Cursor makes decisions based on data
|
580
|
+
return raw_results
|
591
581
|
|
592
582
|
except Exception as e:
|
593
583
|
self.logger.error(f"Mockup comparison failed: {e}")
|
@@ -0,0 +1,53 @@
|
|
1
|
+
"""
|
2
|
+
JSON Serialization Utilities
|
3
|
+
|
4
|
+
Handles conversion of numpy and other non-standard types to JSON-safe Python types.
|
5
|
+
Essential for mockup comparison and any feature using PIL/numpy for image analysis.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import json
|
9
|
+
from typing import Any
|
10
|
+
from pathlib import Path
|
11
|
+
|
12
|
+
|
13
|
+
def safe_json_serialize(obj: Any) -> Any:
|
14
|
+
"""
|
15
|
+
Convert any Python/numpy type to JSON-safe type
|
16
|
+
|
17
|
+
Handles:
|
18
|
+
- numpy scalars (bool_, int_, float_)
|
19
|
+
- numpy arrays (ndarray)
|
20
|
+
- Other non-serializable types (converts to string)
|
21
|
+
|
22
|
+
Args:
|
23
|
+
obj: Any object that might not be JSON serializable
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
JSON-safe Python type
|
27
|
+
"""
|
28
|
+
# numpy scalars (bool_, int64, float64, etc.)
|
29
|
+
if hasattr(obj, 'item'):
|
30
|
+
return obj.item()
|
31
|
+
|
32
|
+
# numpy arrays
|
33
|
+
if hasattr(obj, 'tolist'):
|
34
|
+
return obj.tolist()
|
35
|
+
|
36
|
+
# Fallback for anything else
|
37
|
+
return str(obj)
|
38
|
+
|
39
|
+
|
40
|
+
def safe_json_dump(data: Any, file_path: str, indent: int = 2) -> None:
|
41
|
+
"""
|
42
|
+
Safely dump data to JSON file with numpy type handling
|
43
|
+
|
44
|
+
Args:
|
45
|
+
data: Data to serialize
|
46
|
+
file_path: Path to output file
|
47
|
+
indent: JSON indentation level
|
48
|
+
"""
|
49
|
+
file_path = Path(file_path)
|
50
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
51
|
+
|
52
|
+
with open(file_path, 'w') as f:
|
53
|
+
json.dump(data, f, indent=indent, default=safe_json_serialize)
|
@@ -11,6 +11,8 @@ import json
|
|
11
11
|
from typing import Dict, List, Optional, Any, Tuple
|
12
12
|
from pathlib import Path
|
13
13
|
import logging
|
14
|
+
|
15
|
+
from .json_utils import safe_json_dump, safe_json_serialize
|
14
16
|
try:
|
15
17
|
from PIL import Image, ImageDraw, ImageChops
|
16
18
|
import numpy as np
|
@@ -107,9 +109,14 @@ class MockupComparator:
|
|
107
109
|
await mockup_browser.set_viewport(viewport["width"], viewport["height"])
|
108
110
|
await implementation_browser.set_viewport(viewport["width"], viewport["height"])
|
109
111
|
|
110
|
-
# Navigate to initial pages
|
112
|
+
# Navigate to initial pages with stabilization
|
111
113
|
await mockup_browser.navigate("/")
|
114
|
+
await asyncio.sleep(1)
|
115
|
+
await mockup_browser.page.wait_for_load_state("domcontentloaded")
|
116
|
+
|
112
117
|
await implementation_browser.navigate("/")
|
118
|
+
await asyncio.sleep(1)
|
119
|
+
await implementation_browser.page.wait_for_load_state("domcontentloaded")
|
113
120
|
|
114
121
|
# Execute any required actions on mockup
|
115
122
|
if mockup_actions:
|
@@ -119,6 +126,9 @@ class MockupComparator:
|
|
119
126
|
if implementation_actions:
|
120
127
|
await self._execute_actions_on_browser(implementation_browser, implementation_actions)
|
121
128
|
|
129
|
+
# Final stabilization before capturing
|
130
|
+
await asyncio.sleep(0.5)
|
131
|
+
|
122
132
|
# Capture screenshots
|
123
133
|
mockup_screenshot = await self._capture_comparison_screenshot(
|
124
134
|
mockup_browser, f"{comparison_name}_mockup_{viewport_name}"
|
@@ -163,19 +173,12 @@ class MockupComparator:
|
|
163
173
|
"implementation_url": implementation_url,
|
164
174
|
"viewports_tested": len(viewports),
|
165
175
|
"results": comparison_results,
|
166
|
-
"summary": self._create_comparison_summary(comparison_results)
|
167
|
-
"recommendations": self._generate_improvement_recommendations(comparison_results)
|
176
|
+
"summary": self._create_comparison_summary(comparison_results)
|
168
177
|
}
|
169
178
|
|
170
|
-
# Save comparison report
|
179
|
+
# Save comparison report with safe serialization
|
171
180
|
report_path = self.artifacts_base / "mockup_comparisons" / f"{comparison_name}.json"
|
172
|
-
|
173
|
-
# Custom JSON encoder to handle numpy booleans/integers
|
174
|
-
def json_serializer(obj):
|
175
|
-
if hasattr(obj, 'item'): # numpy types
|
176
|
-
return obj.item()
|
177
|
-
return str(obj)
|
178
|
-
json.dump(comparison_report, f, indent=2, default=json_serializer)
|
181
|
+
safe_json_dump(comparison_report, str(report_path))
|
179
182
|
|
180
183
|
self.logger.info(f"Mockup comparison completed: {comparison_name}")
|
181
184
|
return comparison_report
|
@@ -293,10 +296,9 @@ class MockupComparator:
|
|
293
296
|
"final_recommendations": self._generate_final_recommendations(iteration_results)
|
294
297
|
}
|
295
298
|
|
296
|
-
# Save iteration report
|
299
|
+
# Save iteration report with safe JSON serialization
|
297
300
|
report_path = self.artifacts_base / "iteration_progress" / f"{iteration_session_id}.json"
|
298
|
-
|
299
|
-
json.dump(iteration_report, f, indent=2, default=str)
|
301
|
+
safe_json_dump(iteration_report, str(report_path))
|
300
302
|
|
301
303
|
self.logger.info(f"UI matching iteration completed: {iteration_session_id}")
|
302
304
|
return iteration_report
|
@@ -314,11 +316,15 @@ class MockupComparator:
|
|
314
316
|
if "navigate" in action:
|
315
317
|
path = action["navigate"]
|
316
318
|
await browser.navigate(path)
|
319
|
+
# Wait for page to stabilize after navigation
|
320
|
+
await asyncio.sleep(1) # Brief pause for dynamic content rendering
|
321
|
+
await browser.page.wait_for_load_state("domcontentloaded")
|
317
322
|
elif "click" in action:
|
318
323
|
selector = action["click"]
|
319
324
|
if isinstance(selector, dict):
|
320
325
|
selector = selector["selector"]
|
321
326
|
await browser.click(selector)
|
327
|
+
await asyncio.sleep(0.5) # Brief pause for UI response
|
322
328
|
elif "wait_for" in action:
|
323
329
|
selector = action["wait_for"]
|
324
330
|
if isinstance(selector, dict):
|
@@ -329,6 +335,7 @@ class MockupComparator:
|
|
329
335
|
elif "scroll" in action:
|
330
336
|
scroll_config = action["scroll"]
|
331
337
|
await browser.page.evaluate(f"window.scrollTo({scroll_config.get('x', 0)}, {scroll_config.get('y', 0)})")
|
338
|
+
await asyncio.sleep(0.3) # Brief pause for scroll rendering
|
332
339
|
|
333
340
|
async def _capture_comparison_screenshot(self, browser: BrowserController, name: str) -> str:
|
334
341
|
"""Capture screenshot for comparison"""
|
@@ -1223,55 +1230,19 @@ class MockupComparator:
|
|
1223
1230
|
|
1224
1231
|
avg_similarity = sum(similarities) / len(similarities) if similarities else 0
|
1225
1232
|
|
1233
|
+
# Pure data summary - no interpretation
|
1226
1234
|
return {
|
1227
1235
|
"average_similarity": round(float(avg_similarity), 2),
|
1228
1236
|
"viewports_tested": len(comparison_results),
|
1229
|
-
"
|
1230
|
-
|
1237
|
+
"similarity_by_viewport": [
|
1238
|
+
{
|
1239
|
+
"viewport": result.get("viewport", {}).get("name", "unknown"),
|
1240
|
+
"similarity": float(result.get("visual_diff", {}).get("similarity_score", 0))
|
1241
|
+
}
|
1242
|
+
for result in comparison_results
|
1243
|
+
]
|
1231
1244
|
}
|
1232
1245
|
|
1233
|
-
def _generate_improvement_recommendations(self, comparison_results: List[Dict]) -> List[Dict]:
|
1234
|
-
"""Generate recommendations for improving implementation to match mockup"""
|
1235
|
-
recommendations = []
|
1236
|
-
|
1237
|
-
for result in comparison_results:
|
1238
|
-
viewport = result.get("viewport", {})
|
1239
|
-
layout_analysis = result.get("layout_analysis", {})
|
1240
|
-
visual_diff = result.get("visual_diff", {})
|
1241
|
-
|
1242
|
-
# Analyze layout differences for recommendations
|
1243
|
-
differences = layout_analysis.get("differences", [])
|
1244
|
-
|
1245
|
-
for diff in differences:
|
1246
|
-
if diff["type"] == "missing_in_implementation":
|
1247
|
-
recommendations.append({
|
1248
|
-
"type": "add_element",
|
1249
|
-
"priority": "high",
|
1250
|
-
"description": f"Add missing element: {diff['selector']}",
|
1251
|
-
"viewport": viewport.get("name", "unknown")
|
1252
|
-
})
|
1253
|
-
elif diff["type"] == "property_differences":
|
1254
|
-
for prop, prop_diff in diff.get("differences", {}).items():
|
1255
|
-
recommendations.append({
|
1256
|
-
"type": "adjust_property",
|
1257
|
-
"priority": "medium",
|
1258
|
-
"description": f"Adjust {prop} for {diff['selector']}",
|
1259
|
-
"current_value": prop_diff.get("implementation"),
|
1260
|
-
"target_value": prop_diff.get("mockup"),
|
1261
|
-
"viewport": viewport.get("name", "unknown")
|
1262
|
-
})
|
1263
|
-
|
1264
|
-
# Add visual similarity recommendations
|
1265
|
-
similarity = visual_diff.get("similarity_score", 0)
|
1266
|
-
if similarity < 70:
|
1267
|
-
recommendations.append({
|
1268
|
-
"type": "major_visual_changes",
|
1269
|
-
"priority": "high",
|
1270
|
-
"description": f"Significant visual differences detected (similarity: {similarity}%)",
|
1271
|
-
"viewport": viewport.get("name", "unknown")
|
1272
|
-
})
|
1273
|
-
|
1274
|
-
return recommendations
|
1275
1246
|
|
1276
1247
|
def _create_iteration_summary(self, baseline_comparison: Dict, iteration_results: List[Dict]) -> Dict[str, Any]:
|
1277
1248
|
"""Create summary of iteration session"""
|
@@ -425,6 +425,31 @@ cursorflow measure -u http://localhost:3000 -s "#panel"
|
|
425
425
|
# Output: 532w × 900h ✅
|
426
426
|
```
|
427
427
|
|
428
|
+
### Visual Comparison & Iteration
|
429
|
+
```bash
|
430
|
+
# Compare mockup to implementation (pure measurement)
|
431
|
+
cursorflow compare-mockup MOCKUP_URL -u BASE_URL -ia '[{"navigate": "/page"}]'
|
432
|
+
# Output: Similarity percentage, diff images, element data
|
433
|
+
|
434
|
+
# Test CSS variations (observe real rendering)
|
435
|
+
cursorflow iterate-mockup MOCKUP_URL -u BASE_URL --css-improvements '[
|
436
|
+
{"name": "test1", "css": ".header { padding: 2rem; }"},
|
437
|
+
{"name": "test2", "css": ".header { padding: 1rem; }"}
|
438
|
+
]'
|
439
|
+
# Output: Similarity for each variation - Cursor decides which to apply
|
440
|
+
```
|
441
|
+
|
442
|
+
**Philosophy**:
|
443
|
+
- CursorFlow observes mockup and implementation (both are reality)
|
444
|
+
- Provides quantified measurements (similarity %, diff images, element data)
|
445
|
+
- Temporarily injects CSS to observe what reality WOULD look like
|
446
|
+
- Cursor analyzes the data and makes decisions
|
447
|
+
|
448
|
+
**When to use**:
|
449
|
+
- User has design mockup and needs to match it
|
450
|
+
- Testing multiple CSS approaches before applying
|
451
|
+
- Measuring progress toward design specifications
|
452
|
+
|
428
453
|
## Analyzing Results
|
429
454
|
|
430
455
|
### **Hot Reload CSS Iteration Results**
|
@@ -24,6 +24,7 @@ cursorflow/core/error_correlator.py
|
|
24
24
|
cursorflow/core/event_correlator.py
|
25
25
|
cursorflow/core/file_change_monitor.py
|
26
26
|
cursorflow/core/hmr_detector.py
|
27
|
+
cursorflow/core/json_utils.py
|
27
28
|
cursorflow/core/log_collector.py
|
28
29
|
cursorflow/core/log_monitor.py
|
29
30
|
cursorflow/core/mockup_comparator.py
|
@@ -322,6 +322,114 @@ cursorflow measure -u http://localhost:3000 -s "#panel"
|
|
322
322
|
# Output: 532w × 900h ✅ Fixed!
|
323
323
|
```
|
324
324
|
|
325
|
+
### **Visual Comparison Commands**
|
326
|
+
|
327
|
+
CursorFlow provides visual comparison tools for iterating toward design specifications through pure measurement.
|
328
|
+
|
329
|
+
#### **`compare-mockup` - Visual Design Comparison**
|
330
|
+
|
331
|
+
Compare a design mockup against your work-in-progress implementation:
|
332
|
+
|
333
|
+
**What you get (pure data)**:
|
334
|
+
- **Screenshots** - Both mockup and implementation captured
|
335
|
+
- **Visual diff images** - Pixel-by-pixel difference highlighting
|
336
|
+
- **Similarity percentage** - Quantified visual match (0-100%)
|
337
|
+
- **Element position data** - X, Y coordinates for both versions
|
338
|
+
- **Size measurements** - Width, height comparisons
|
339
|
+
- **CSS property data** - Computed styles for matching elements
|
340
|
+
|
341
|
+
**Philosophy**: CursorFlow observes both realities (mockup + implementation) and provides measurements. Cursor analyzes the data and decides what changes to make.
|
342
|
+
|
343
|
+
**Basic usage**:
|
344
|
+
```bash
|
345
|
+
cursorflow compare-mockup https://mockup.example.com/dashboard \
|
346
|
+
--base-url http://localhost:3000 \
|
347
|
+
--output comparison-results.json
|
348
|
+
```
|
349
|
+
|
350
|
+
**With custom actions**:
|
351
|
+
```bash
|
352
|
+
cursorflow compare-mockup https://mockup.example.com/dashboard \
|
353
|
+
--base-url http://localhost:3000 \
|
354
|
+
--mockup-actions '[{"navigate": "/dashboard"}]' \
|
355
|
+
--implementation-actions '[{"navigate": "/dashboard"}, {"wait_for": "#main-content"}]'
|
356
|
+
```
|
357
|
+
|
358
|
+
**With multiple viewports**:
|
359
|
+
```bash
|
360
|
+
cursorflow compare-mockup https://mockup.example.com \
|
361
|
+
--base-url http://localhost:3000 \
|
362
|
+
--viewports '[
|
363
|
+
{"width": 1440, "height": 900, "name": "desktop"},
|
364
|
+
{"width": 768, "height": 1024, "name": "tablet"}
|
365
|
+
]'
|
366
|
+
```
|
367
|
+
|
368
|
+
**Output structure**:
|
369
|
+
```json
|
370
|
+
{
|
371
|
+
"comparison_id": "mockup_comparison_123456",
|
372
|
+
"mockup_url": "https://mockup.example.com",
|
373
|
+
"implementation_url": "http://localhost:3000",
|
374
|
+
"summary": {
|
375
|
+
"average_similarity": 87.73,
|
376
|
+
"viewports_tested": 2,
|
377
|
+
"similarity_by_viewport": [
|
378
|
+
{"viewport": "desktop", "similarity": 89.5},
|
379
|
+
{"viewport": "tablet", "similarity": 85.96}
|
380
|
+
]
|
381
|
+
},
|
382
|
+
"results": [
|
383
|
+
{
|
384
|
+
"viewport": {"width": 1440, "height": 900, "name": "desktop"},
|
385
|
+
"mockup_screenshot": "path/to/mockup.png",
|
386
|
+
"implementation_screenshot": "path/to/impl.png",
|
387
|
+
"visual_diff": {
|
388
|
+
"similarity_score": 89.5,
|
389
|
+
"different_pixels": 45000,
|
390
|
+
"total_pixels": 1296000,
|
391
|
+
"diff_image": "path/to/diff.png",
|
392
|
+
"highlighted_diff": "path/to/highlighted.png"
|
393
|
+
},
|
394
|
+
"layout_analysis": {
|
395
|
+
"mockup_elements": 45,
|
396
|
+
"implementation_elements": 52,
|
397
|
+
"differences": [...]
|
398
|
+
}
|
399
|
+
}
|
400
|
+
]
|
401
|
+
}
|
402
|
+
```
|
403
|
+
|
404
|
+
**Use cases**:
|
405
|
+
- Compare implementation to Figma exports
|
406
|
+
- Verify design system component accuracy
|
407
|
+
- Measure progress toward design specifications
|
408
|
+
- Document visual differences for stakeholders
|
409
|
+
|
410
|
+
#### **`iterate-mockup` - CSS Iteration with Measurement**
|
411
|
+
|
412
|
+
Test multiple CSS changes and observe which gets closer to the mockup:
|
413
|
+
|
414
|
+
**Basic usage**:
|
415
|
+
```bash
|
416
|
+
cursorflow iterate-mockup https://mockup.example.com/dashboard \
|
417
|
+
--base-url http://localhost:3000 \
|
418
|
+
--css-improvements '[
|
419
|
+
{"name": "spacing-fix", "css": ".header { padding: 2rem; }"},
|
420
|
+
{"name": "color-adjust", "css": ".btn { background: #007bff; }"}
|
421
|
+
]'
|
422
|
+
```
|
423
|
+
|
424
|
+
**What it does**:
|
425
|
+
1. Captures baseline similarity
|
426
|
+
2. Temporarily injects each CSS change
|
427
|
+
3. Observes the REAL rendered result
|
428
|
+
4. Captures similarity for each variation
|
429
|
+
5. Provides measurements for Cursor to analyze
|
430
|
+
|
431
|
+
**Output**: Similarity data for each CSS variation (Cursor decides which to apply)
|
432
|
+
|
325
433
|
### **Artifact Management**
|
326
434
|
|
327
435
|
CursorFlow generates screenshots, traces, and session data. Clean up regularly:
|
@@ -349,6 +457,96 @@ cursorflow cleanup --all --dry-run
|
|
349
457
|
|
350
458
|
**Typical growth:** 50-100MB/day light usage, 500MB-1GB/day heavy usage
|
351
459
|
|
460
|
+
### **Visual Comparison Commands**
|
461
|
+
|
462
|
+
CursorFlow provides mockup comparison for visual iteration - comparing your implementation to design specifications through pure data collection.
|
463
|
+
|
464
|
+
#### **compare-mockup - Visual Measurement**
|
465
|
+
|
466
|
+
Compare two URLs and get quantified similarity data:
|
467
|
+
|
468
|
+
```bash
|
469
|
+
# Basic comparison
|
470
|
+
cursorflow compare-mockup "https://mockup.example.com" \
|
471
|
+
--base-url http://localhost:3000
|
472
|
+
|
473
|
+
# With custom actions
|
474
|
+
cursorflow compare-mockup "https://mockup.example.com" \
|
475
|
+
--base-url http://localhost:3000 \
|
476
|
+
--implementation-actions '[{"navigate": "/dashboard"}]'
|
477
|
+
|
478
|
+
# Multiple viewports
|
479
|
+
cursorflow compare-mockup "https://mockup.example.com" \
|
480
|
+
--base-url http://localhost:3000 \
|
481
|
+
--viewports '[{"width": 1440, "height": 900, "name": "desktop"}, {"width": 375, "height": 667, "name": "mobile"}]'
|
482
|
+
```
|
483
|
+
|
484
|
+
**Output Data Structure**:
|
485
|
+
```json
|
486
|
+
{
|
487
|
+
"comparison_id": "mockup_comparison_123456",
|
488
|
+
"mockup_url": "https://mockup.example.com",
|
489
|
+
"implementation_url": "http://localhost:3000",
|
490
|
+
"results": [
|
491
|
+
{
|
492
|
+
"viewport": {"width": 1440, "height": 900, "name": "desktop"},
|
493
|
+
"mockup_screenshot": "path/to/mockup.png",
|
494
|
+
"implementation_screenshot": "path/to/impl.png",
|
495
|
+
"visual_diff": {
|
496
|
+
"diff_image": "path/to/diff.png",
|
497
|
+
"highlighted_diff": "path/to/highlighted.png",
|
498
|
+
"similarity_score": 87.3,
|
499
|
+
"different_pixels": 45230,
|
500
|
+
"total_pixels": 1296000
|
501
|
+
}
|
502
|
+
}
|
503
|
+
],
|
504
|
+
"summary": {
|
505
|
+
"average_similarity": 87.3,
|
506
|
+
"viewports_tested": 1,
|
507
|
+
"similarity_by_viewport": [...]
|
508
|
+
}
|
509
|
+
}
|
510
|
+
```
|
511
|
+
|
512
|
+
**Philosophy**: Pure data collection - provides measurements, Cursor interprets them.
|
513
|
+
|
514
|
+
#### **iterate-mockup - CSS Experimentation**
|
515
|
+
|
516
|
+
Test multiple CSS variations and observe real outcomes:
|
517
|
+
|
518
|
+
```bash
|
519
|
+
# Create CSS improvements JSON
|
520
|
+
cat > improvements.json << 'EOF'
|
521
|
+
[
|
522
|
+
{
|
523
|
+
"name": "fix-spacing",
|
524
|
+
"css": ".container { padding: 2rem; gap: 1.5rem; }"
|
525
|
+
},
|
526
|
+
{
|
527
|
+
"name": "adjust-colors",
|
528
|
+
"css": ".btn-primary { background: #007bff; }"
|
529
|
+
}
|
530
|
+
]
|
531
|
+
EOF
|
532
|
+
|
533
|
+
# Run iteration
|
534
|
+
cursorflow iterate-mockup "https://mockup.example.com" \
|
535
|
+
--base-url http://localhost:3000 \
|
536
|
+
--css-improvements improvements.json
|
537
|
+
```
|
538
|
+
|
539
|
+
**What it does**:
|
540
|
+
1. Captures baseline comparison
|
541
|
+
2. Temporarily injects each CSS variation
|
542
|
+
3. Captures screenshot of REAL outcome
|
543
|
+
4. Measures similarity for each variation
|
544
|
+
5. Provides quantified data for each experiment
|
545
|
+
|
546
|
+
**Output**: Similarity percentages for each CSS variation, Cursor decides which to apply.
|
547
|
+
|
548
|
+
**Use case**: Rapid CSS experimentation with quantified feedback.
|
549
|
+
|
352
550
|
## ⚡ **Quick Usage Examples**
|
353
551
|
|
354
552
|
### **OpenSAS/Mod_Perl (Our Current Project)**
|
@@ -0,0 +1,195 @@
|
|
1
|
+
"""
|
2
|
+
Mockup Comparison Example
|
3
|
+
|
4
|
+
Demonstrates pure observation approach to visual design matching.
|
5
|
+
CursorFlow provides measurements - Cursor makes decisions based on data.
|
6
|
+
|
7
|
+
Philosophy: We observe multiple realities (mockup, implementation, CSS variations)
|
8
|
+
and provide quantified similarity metrics. No interpretation, just data.
|
9
|
+
"""
|
10
|
+
|
11
|
+
import asyncio
|
12
|
+
import json
|
13
|
+
from pathlib import Path
|
14
|
+
import sys
|
15
|
+
|
16
|
+
# Add parent directory to path for imports
|
17
|
+
sys.path.append(str(Path(__file__).parent.parent))
|
18
|
+
|
19
|
+
from cursorflow.core.cursorflow import CursorFlow
|
20
|
+
|
21
|
+
|
22
|
+
async def basic_mockup_comparison():
|
23
|
+
"""
|
24
|
+
Basic example: Compare mockup to implementation with pure data collection
|
25
|
+
"""
|
26
|
+
print("=" * 60)
|
27
|
+
print("🎨 Basic Mockup Comparison - Pure Observation")
|
28
|
+
print("=" * 60)
|
29
|
+
|
30
|
+
# Initialize CursorFlow
|
31
|
+
flow = CursorFlow(
|
32
|
+
base_url="http://localhost:3000", # Your work-in-progress
|
33
|
+
log_config={'source': 'local', 'paths': []},
|
34
|
+
browser_config={'headless': True}
|
35
|
+
)
|
36
|
+
|
37
|
+
# Compare mockup to implementation (observing both realities)
|
38
|
+
print("\n📸 Capturing both mockup and implementation...")
|
39
|
+
results = await flow.compare_mockup_to_implementation(
|
40
|
+
mockup_url="https://example.com", # Simulating mockup
|
41
|
+
implementation_actions=[
|
42
|
+
{"navigate": "/"},
|
43
|
+
{"wait_for": "body"}
|
44
|
+
],
|
45
|
+
comparison_config={
|
46
|
+
"viewports": [
|
47
|
+
{"width": 1440, "height": 900, "name": "desktop"}
|
48
|
+
],
|
49
|
+
"diff_threshold": 0.1
|
50
|
+
}
|
51
|
+
)
|
52
|
+
|
53
|
+
if "error" in results:
|
54
|
+
print(f"❌ Comparison failed: {results['error']}")
|
55
|
+
return
|
56
|
+
|
57
|
+
# Display pure metrics (no interpretation)
|
58
|
+
summary = results.get('summary', {})
|
59
|
+
print(f"\n✅ Comparison completed: {results.get('comparison_id')}")
|
60
|
+
print(f"\n📊 Measurement Data:")
|
61
|
+
print(f" Average similarity: {summary.get('average_similarity', 0)}%")
|
62
|
+
print(f" Viewports tested: {summary.get('viewports_tested', 0)}")
|
63
|
+
|
64
|
+
# Show per-viewport data
|
65
|
+
similarity_by_viewport = summary.get('similarity_by_viewport', [])
|
66
|
+
for viewport_data in similarity_by_viewport:
|
67
|
+
print(f" {viewport_data['viewport']}: {viewport_data['similarity']}%")
|
68
|
+
|
69
|
+
# Show what artifacts were created
|
70
|
+
print(f"\n📁 Artifacts created:")
|
71
|
+
for result in results.get('results', []):
|
72
|
+
print(f" Mockup screenshot: {Path(result['mockup_screenshot']).name}")
|
73
|
+
print(f" Implementation screenshot: {Path(result['implementation_screenshot']).name}")
|
74
|
+
print(f" Diff image: {Path(result['visual_diff']['diff_image']).name}")
|
75
|
+
print(f" Highlighted diff: {Path(result['visual_diff']['highlighted_diff']).name}")
|
76
|
+
|
77
|
+
print(f"\n💡 Cursor can now analyze this data to decide on CSS changes")
|
78
|
+
print(f" Data saved to: .cursorflow/artifacts/mockup_comparisons/")
|
79
|
+
|
80
|
+
|
81
|
+
async def css_iteration_with_measurements():
|
82
|
+
"""
|
83
|
+
Example: Test multiple CSS variations and observe real outcomes
|
84
|
+
"""
|
85
|
+
print("\n" + "=" * 60)
|
86
|
+
print("🔄 CSS Iteration - Observing Multiple Realities")
|
87
|
+
print("=" * 60)
|
88
|
+
|
89
|
+
flow = CursorFlow(
|
90
|
+
base_url="http://localhost:3000",
|
91
|
+
log_config={'source': 'local', 'paths': []},
|
92
|
+
browser_config={'headless': True}
|
93
|
+
)
|
94
|
+
|
95
|
+
# Define CSS variations to test
|
96
|
+
css_improvements = [
|
97
|
+
{
|
98
|
+
"name": "generous-spacing",
|
99
|
+
"css": ".container { padding: 2rem; gap: 2rem; }"
|
100
|
+
},
|
101
|
+
{
|
102
|
+
"name": "moderate-spacing",
|
103
|
+
"css": ".container { padding: 1.5rem; gap: 1.5rem; }"
|
104
|
+
},
|
105
|
+
{
|
106
|
+
"name": "tight-spacing",
|
107
|
+
"css": ".container { padding: 1rem; gap: 1rem; }"
|
108
|
+
}
|
109
|
+
]
|
110
|
+
|
111
|
+
print(f"\n🧪 Testing {len(css_improvements)} CSS variations...")
|
112
|
+
print(" (Temporarily injecting CSS to observe real rendering)")
|
113
|
+
|
114
|
+
# Run iterative comparison
|
115
|
+
results = await flow.iterative_mockup_matching(
|
116
|
+
mockup_url="https://example.com",
|
117
|
+
css_improvements=css_improvements,
|
118
|
+
base_actions=[{"navigate": "/"}, {"wait_for": "body"}]
|
119
|
+
)
|
120
|
+
|
121
|
+
if "error" in results:
|
122
|
+
print(f"❌ Iteration failed: {results['error']}")
|
123
|
+
return
|
124
|
+
|
125
|
+
# Display measurements for each variation
|
126
|
+
print(f"\n📊 Similarity Measurements:")
|
127
|
+
|
128
|
+
baseline_sim = results.get('baseline', {}).get('visual_diff', {}).get('similarity_score', 0)
|
129
|
+
print(f" Baseline (no changes): {baseline_sim}%")
|
130
|
+
|
131
|
+
for iteration in results.get('iterations', []):
|
132
|
+
name = iteration.get('css_change', {}).get('name', 'unknown')
|
133
|
+
similarity = iteration.get('visual_diff', {}).get('similarity_score', 0)
|
134
|
+
diff = similarity - baseline_sim
|
135
|
+
print(f" {name}: {similarity}% ({diff:+.1f}%)")
|
136
|
+
|
137
|
+
print(f"\n💡 Pure data provided - Cursor analyzes and decides which CSS to apply")
|
138
|
+
print(f" Each variation measured against REAL rendering")
|
139
|
+
|
140
|
+
|
141
|
+
async def understanding_the_data():
|
142
|
+
"""
|
143
|
+
Example: What data is available in comparison results
|
144
|
+
"""
|
145
|
+
print("\n" + "=" * 60)
|
146
|
+
print("📋 Understanding Comparison Data Structure")
|
147
|
+
print("=" * 60)
|
148
|
+
|
149
|
+
print("""
|
150
|
+
CursorFlow provides these measurements:
|
151
|
+
|
152
|
+
1. Visual Metrics:
|
153
|
+
- similarity_percentage: 0-100 (quantified match)
|
154
|
+
- different_pixels: Count of differing pixels
|
155
|
+
- total_pixels: Total canvas size
|
156
|
+
- major_difference_regions: [{x, y, width, height, area}]
|
157
|
+
|
158
|
+
2. Screenshots:
|
159
|
+
- mockup_screenshot: Path to mockup capture
|
160
|
+
- implementation_screenshot: Path to implementation
|
161
|
+
- diff_image: Pixel-by-pixel difference
|
162
|
+
- highlighted_diff: Visual diff with red overlay
|
163
|
+
|
164
|
+
3. Layout Data:
|
165
|
+
- mockup_elements: Count of elements in mockup
|
166
|
+
- implementation_elements: Count in implementation
|
167
|
+
- differences: Position/size/style variances
|
168
|
+
|
169
|
+
4. Per-Viewport Data:
|
170
|
+
- Separate measurements for each breakpoint
|
171
|
+
- Responsive behavior captured
|
172
|
+
|
173
|
+
Cursor uses this data to:
|
174
|
+
- Decide if implementation is close enough
|
175
|
+
- Choose which CSS changes to make
|
176
|
+
- Prioritize layout vs styling fixes
|
177
|
+
- Determine when design match is acceptable
|
178
|
+
""")
|
179
|
+
|
180
|
+
|
181
|
+
if __name__ == '__main__':
|
182
|
+
print("=" * 60)
|
183
|
+
print("CursorFlow Mockup Comparison Examples")
|
184
|
+
print("Pure Observation - Cursor Makes Decisions")
|
185
|
+
print("=" * 60)
|
186
|
+
|
187
|
+
# Run examples
|
188
|
+
asyncio.run(basic_mockup_comparison())
|
189
|
+
asyncio.run(css_iteration_with_measurements())
|
190
|
+
asyncio.run(understanding_the_data())
|
191
|
+
|
192
|
+
print("\n" + "=" * 60)
|
193
|
+
print("✅ All examples completed!")
|
194
|
+
print("💡 CursorFlow observes reality - Cursor analyzes and decides")
|
195
|
+
print("=" * 60)
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "cursorflow"
|
7
|
-
version = "2.
|
7
|
+
version = "2.6.0"
|
8
8
|
description = "🔥 Complete page intelligence for AI-driven development with Hot Reload Intelligence - captures DOM, network, console, performance, HMR events, and comprehensive page analysis"
|
9
9
|
authors = [
|
10
10
|
{name = "GeekWarrior Development", email = "rbush@cooltheory.com"}
|
@@ -72,6 +72,6 @@ line-length = 88
|
|
72
72
|
target-version = ['py38']
|
73
73
|
|
74
74
|
[tool.mypy]
|
75
|
-
python_version = "2.
|
75
|
+
python_version = "2.6.0"
|
76
76
|
warn_return_any = true
|
77
77
|
warn_unused_configs = true
|
@@ -1,316 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Mockup Comparison Example
|
3
|
-
|
4
|
-
Demonstrates how to use CursorFlow to compare a design mockup with
|
5
|
-
a work-in-progress implementation and iteratively improve the match.
|
6
|
-
"""
|
7
|
-
|
8
|
-
import asyncio
|
9
|
-
import json
|
10
|
-
from pathlib import Path
|
11
|
-
import sys
|
12
|
-
|
13
|
-
# Add parent directory to path for imports
|
14
|
-
sys.path.append(str(Path(__file__).parent.parent))
|
15
|
-
|
16
|
-
from cursorflow.core.cursorflow import CursorFlow
|
17
|
-
|
18
|
-
|
19
|
-
async def basic_mockup_comparison():
|
20
|
-
"""
|
21
|
-
Basic example: Compare a mockup to current implementation
|
22
|
-
"""
|
23
|
-
print("🎨 Basic Mockup Comparison Example")
|
24
|
-
print("=" * 50)
|
25
|
-
|
26
|
-
# Initialize CursorFlow
|
27
|
-
flow = CursorFlow(
|
28
|
-
base_url="http://localhost:3000", # Your work-in-progress implementation
|
29
|
-
log_config={'source': 'local', 'paths': ['logs/app.log']},
|
30
|
-
browser_config={'headless': True}
|
31
|
-
)
|
32
|
-
|
33
|
-
# Compare mockup to implementation
|
34
|
-
results = await flow.compare_mockup_to_implementation(
|
35
|
-
mockup_url="https://mockup.example.com/dashboard", # Replace with your mockup URL
|
36
|
-
mockup_actions=[
|
37
|
-
{"navigate": "/dashboard"},
|
38
|
-
{"wait_for": "#main-content"},
|
39
|
-
{"screenshot": "mockup_state"}
|
40
|
-
],
|
41
|
-
implementation_actions=[
|
42
|
-
{"navigate": "/dashboard"},
|
43
|
-
{"wait_for": "#main-content"},
|
44
|
-
{"screenshot": "implementation_state"}
|
45
|
-
],
|
46
|
-
comparison_config={
|
47
|
-
"viewports": [
|
48
|
-
{"width": 1440, "height": 900, "name": "desktop"},
|
49
|
-
{"width": 768, "height": 1024, "name": "tablet"},
|
50
|
-
{"width": 375, "height": 667, "name": "mobile"}
|
51
|
-
],
|
52
|
-
"diff_threshold": 0.1 # 10% difference threshold
|
53
|
-
}
|
54
|
-
)
|
55
|
-
|
56
|
-
if "error" in results:
|
57
|
-
print(f"❌ Comparison failed: {results['error']}")
|
58
|
-
return
|
59
|
-
|
60
|
-
# Display results
|
61
|
-
summary = results.get('summary', {})
|
62
|
-
print(f"✅ Comparison completed: {results.get('comparison_id')}")
|
63
|
-
print(f"📊 Average similarity: {summary.get('average_similarity', 0)}%")
|
64
|
-
print(f"📱 Viewports tested: {summary.get('viewports_tested', 0)}")
|
65
|
-
|
66
|
-
# Show recommendations
|
67
|
-
recommendations = results.get('recommendations', [])
|
68
|
-
if recommendations:
|
69
|
-
print(f"\n💡 Recommendations ({len(recommendations)} total):")
|
70
|
-
for i, rec in enumerate(recommendations[:5]): # Show first 5
|
71
|
-
print(f" {i+1}. {rec.get('description', 'No description')}")
|
72
|
-
|
73
|
-
# Save results for analysis
|
74
|
-
from pathlib import Path
|
75
|
-
artifacts_dir = Path('.cursorflow/artifacts')
|
76
|
-
artifacts_dir.mkdir(parents=True, exist_ok=True)
|
77
|
-
|
78
|
-
results_file = artifacts_dir / 'basic_mockup_comparison_results.json'
|
79
|
-
with open(results_file, 'w') as f:
|
80
|
-
json.dump(results, f, indent=2, default=str)
|
81
|
-
|
82
|
-
print(f"\n💾 Full results saved to: {results_file}")
|
83
|
-
print(f"📁 Visual diffs available in: .cursorflow/artifacts/")
|
84
|
-
|
85
|
-
return results
|
86
|
-
|
87
|
-
|
88
|
-
async def iterative_mockup_matching():
|
89
|
-
"""
|
90
|
-
Advanced example: Iteratively improve implementation to match mockup
|
91
|
-
"""
|
92
|
-
print("\n🔄 Iterative Mockup Matching Example")
|
93
|
-
print("=" * 50)
|
94
|
-
|
95
|
-
# Initialize CursorFlow
|
96
|
-
flow = CursorFlow(
|
97
|
-
base_url="http://localhost:3000",
|
98
|
-
log_config={'source': 'local', 'paths': ['logs/app.log']},
|
99
|
-
browser_config={'headless': True}
|
100
|
-
)
|
101
|
-
|
102
|
-
# Define CSS improvements to test
|
103
|
-
css_improvements = [
|
104
|
-
{
|
105
|
-
"name": "fix-header-spacing",
|
106
|
-
"css": ".header { padding: 2rem 0; margin-bottom: 1rem; }",
|
107
|
-
"rationale": "Match mockup header spacing and add bottom margin"
|
108
|
-
},
|
109
|
-
{
|
110
|
-
"name": "adjust-button-styles",
|
111
|
-
"css": ".btn-primary { background: #007bff; border-radius: 8px; padding: 12px 24px; }",
|
112
|
-
"rationale": "Match mockup button styling with rounded corners"
|
113
|
-
},
|
114
|
-
{
|
115
|
-
"name": "improve-card-layout",
|
116
|
-
"css": ".card { box-shadow: 0 4px 6px rgba(0,0,0,0.1); border-radius: 12px; }",
|
117
|
-
"rationale": "Add shadow and rounded corners to match mockup cards"
|
118
|
-
},
|
119
|
-
{
|
120
|
-
"name": "fix-typography",
|
121
|
-
"css": "h1 { font-size: 2.5rem; font-weight: 700; color: #1a1a1a; }",
|
122
|
-
"rationale": "Match mockup heading typography"
|
123
|
-
},
|
124
|
-
{
|
125
|
-
"name": "adjust-grid-spacing",
|
126
|
-
"css": ".grid-container { gap: 2rem; padding: 1rem; }",
|
127
|
-
"rationale": "Increase grid spacing to match mockup layout"
|
128
|
-
}
|
129
|
-
]
|
130
|
-
|
131
|
-
# Base actions to perform before each comparison
|
132
|
-
base_actions = [
|
133
|
-
{"navigate": "/dashboard"},
|
134
|
-
{"wait_for": "#main-content"},
|
135
|
-
{"wait": 1} # Let page fully load
|
136
|
-
]
|
137
|
-
|
138
|
-
# Execute iterative matching
|
139
|
-
results = await flow.iterative_mockup_matching(
|
140
|
-
mockup_url="https://mockup.example.com/dashboard", # Replace with your mockup URL
|
141
|
-
css_improvements=css_improvements,
|
142
|
-
base_actions=base_actions,
|
143
|
-
comparison_config={
|
144
|
-
"diff_threshold": 0.08, # Slightly more sensitive
|
145
|
-
"viewports": [
|
146
|
-
{"width": 1440, "height": 900, "name": "desktop"}
|
147
|
-
]
|
148
|
-
}
|
149
|
-
)
|
150
|
-
|
151
|
-
if "error" in results:
|
152
|
-
print(f"❌ Iteration failed: {results['error']}")
|
153
|
-
return
|
154
|
-
|
155
|
-
# Display results
|
156
|
-
summary = results.get('summary', {})
|
157
|
-
print(f"✅ Iteration completed: {results.get('session_id')}")
|
158
|
-
print(f"📊 Total improvement: {summary.get('total_improvement', 0)}%")
|
159
|
-
print(f"🔄 Successful iterations: {summary.get('successful_iterations', 0)}/{summary.get('total_iterations', 0)}")
|
160
|
-
print(f"📈 Average improvement per iteration: {summary.get('average_improvement_per_iteration', 0)}%")
|
161
|
-
|
162
|
-
# Show best iteration
|
163
|
-
best_iteration = results.get('best_iteration')
|
164
|
-
if best_iteration:
|
165
|
-
print(f"\n🏆 Best iteration:")
|
166
|
-
print(f" Name: {best_iteration.get('css_change', {}).get('name', 'unnamed')}")
|
167
|
-
print(f" Similarity achieved: {best_iteration.get('similarity_achieved', 0)}%")
|
168
|
-
print(f" Improvement: +{best_iteration.get('improvement', 0)}%")
|
169
|
-
print(f" CSS: {best_iteration.get('css_change', {}).get('css', 'N/A')}")
|
170
|
-
|
171
|
-
# Show final recommendations
|
172
|
-
recommendations = results.get('final_recommendations', [])
|
173
|
-
if recommendations:
|
174
|
-
print(f"\n💡 Final recommendations ({len(recommendations)} total):")
|
175
|
-
for i, rec in enumerate(recommendations):
|
176
|
-
print(f" {i+1}. [{rec.get('priority', 'medium').upper()}] {rec.get('description', 'No description')}")
|
177
|
-
|
178
|
-
# Show successful CSS changes to apply
|
179
|
-
successful_iterations = [
|
180
|
-
iteration for iteration in results.get('iterations', [])
|
181
|
-
if iteration.get('improvement_metrics', {}).get('is_improvement', False)
|
182
|
-
]
|
183
|
-
|
184
|
-
if successful_iterations:
|
185
|
-
print(f"\n🎯 CSS Changes to Apply ({len(successful_iterations)} successful):")
|
186
|
-
for iteration in successful_iterations:
|
187
|
-
css_change = iteration.get('css_change', {})
|
188
|
-
improvement = iteration.get('improvement_metrics', {}).get('improvement', 0)
|
189
|
-
print(f" ✅ {css_change.get('name', 'unnamed')} (+{improvement:.1f}%)")
|
190
|
-
print(f" CSS: {css_change.get('css', 'N/A')}")
|
191
|
-
print(f" Rationale: {css_change.get('rationale', 'N/A')}")
|
192
|
-
print()
|
193
|
-
|
194
|
-
# Save results for analysis
|
195
|
-
from pathlib import Path
|
196
|
-
artifacts_dir = Path('.cursorflow/artifacts')
|
197
|
-
artifacts_dir.mkdir(parents=True, exist_ok=True)
|
198
|
-
|
199
|
-
results_file = artifacts_dir / 'iterative_mockup_matching_results.json'
|
200
|
-
with open(results_file, 'w') as f:
|
201
|
-
json.dump(results, f, indent=2, default=str)
|
202
|
-
|
203
|
-
print(f"💾 Full results saved to: {results_file}")
|
204
|
-
print(f"📁 Iteration progress available in: .cursorflow/artifacts/")
|
205
|
-
|
206
|
-
return results
|
207
|
-
|
208
|
-
|
209
|
-
async def responsive_mockup_comparison():
|
210
|
-
"""
|
211
|
-
Example: Compare mockup across multiple responsive breakpoints
|
212
|
-
"""
|
213
|
-
print("\n📱 Responsive Mockup Comparison Example")
|
214
|
-
print("=" * 50)
|
215
|
-
|
216
|
-
# Initialize CursorFlow
|
217
|
-
flow = CursorFlow(
|
218
|
-
base_url="http://localhost:3000",
|
219
|
-
log_config={'source': 'local', 'paths': ['logs/app.log']},
|
220
|
-
browser_config={'headless': True}
|
221
|
-
)
|
222
|
-
|
223
|
-
# Test across multiple viewports
|
224
|
-
results = await flow.compare_mockup_to_implementation(
|
225
|
-
mockup_url="https://mockup.example.com/responsive-page",
|
226
|
-
comparison_config={
|
227
|
-
"viewports": [
|
228
|
-
{"width": 1920, "height": 1080, "name": "large_desktop"},
|
229
|
-
{"width": 1440, "height": 900, "name": "desktop"},
|
230
|
-
{"width": 1024, "height": 768, "name": "tablet_landscape"},
|
231
|
-
{"width": 768, "height": 1024, "name": "tablet_portrait"},
|
232
|
-
{"width": 414, "height": 896, "name": "mobile_large"},
|
233
|
-
{"width": 375, "height": 667, "name": "mobile_medium"},
|
234
|
-
{"width": 320, "height": 568, "name": "mobile_small"}
|
235
|
-
],
|
236
|
-
"diff_threshold": 0.12 # Slightly more tolerant for responsive
|
237
|
-
}
|
238
|
-
)
|
239
|
-
|
240
|
-
if "error" in results:
|
241
|
-
print(f"❌ Responsive comparison failed: {results['error']}")
|
242
|
-
return
|
243
|
-
|
244
|
-
# Analyze results by viewport
|
245
|
-
print("📊 Responsive Comparison Results:")
|
246
|
-
for result in results.get('results', []):
|
247
|
-
viewport = result.get('viewport', {})
|
248
|
-
visual_diff = result.get('visual_diff', {})
|
249
|
-
similarity = visual_diff.get('similarity_score', 0)
|
250
|
-
|
251
|
-
status = "✅" if similarity > 80 else "⚠️" if similarity > 60 else "❌"
|
252
|
-
print(f" {status} {viewport.get('name', 'unknown')}: {similarity}% similarity")
|
253
|
-
|
254
|
-
# Find best and worst performing viewports
|
255
|
-
viewport_results = results.get('results', [])
|
256
|
-
if viewport_results:
|
257
|
-
best_viewport = max(viewport_results, key=lambda x: x.get('visual_diff', {}).get('similarity_score', 0))
|
258
|
-
worst_viewport = min(viewport_results, key=lambda x: x.get('visual_diff', {}).get('similarity_score', 0))
|
259
|
-
|
260
|
-
print(f"\n🏆 Best match: {best_viewport.get('viewport', {}).get('name', 'unknown')} "
|
261
|
-
f"({best_viewport.get('visual_diff', {}).get('similarity_score', 0)}%)")
|
262
|
-
print(f"🔧 Needs work: {worst_viewport.get('viewport', {}).get('name', 'unknown')} "
|
263
|
-
f"({worst_viewport.get('visual_diff', {}).get('similarity_score', 0)}%)")
|
264
|
-
|
265
|
-
# Save results
|
266
|
-
from pathlib import Path
|
267
|
-
artifacts_dir = Path('.cursorflow/artifacts')
|
268
|
-
artifacts_dir.mkdir(parents=True, exist_ok=True)
|
269
|
-
|
270
|
-
results_file = artifacts_dir / 'responsive_mockup_comparison_results.json'
|
271
|
-
with open(results_file, 'w') as f:
|
272
|
-
json.dump(results, f, indent=2, default=str)
|
273
|
-
|
274
|
-
print(f"\n💾 Full results saved to: {results_file}")
|
275
|
-
|
276
|
-
return results
|
277
|
-
|
278
|
-
|
279
|
-
async def main():
|
280
|
-
"""
|
281
|
-
Run all mockup comparison examples
|
282
|
-
"""
|
283
|
-
print("🚀 CursorFlow Mockup Comparison Examples")
|
284
|
-
print("=" * 60)
|
285
|
-
print("This demonstrates how to compare mockups with implementations")
|
286
|
-
print("and iteratively improve UI matching.\n")
|
287
|
-
|
288
|
-
try:
|
289
|
-
# Run basic comparison
|
290
|
-
await basic_mockup_comparison()
|
291
|
-
|
292
|
-
# Run iterative matching
|
293
|
-
await iterative_mockup_matching()
|
294
|
-
|
295
|
-
# Run responsive comparison
|
296
|
-
await responsive_mockup_comparison()
|
297
|
-
|
298
|
-
print("\n🎉 All examples completed successfully!")
|
299
|
-
print("\nNext steps:")
|
300
|
-
print("1. Review the generated JSON files for detailed analysis")
|
301
|
-
print("2. Check .cursorflow/artifacts/ for visual diffs and screenshots")
|
302
|
-
print("3. Apply the successful CSS changes to your actual codebase")
|
303
|
-
print("4. Re-run comparisons to validate improvements")
|
304
|
-
|
305
|
-
except Exception as e:
|
306
|
-
print(f"\n❌ Example failed: {e}")
|
307
|
-
import traceback
|
308
|
-
traceback.print_exc()
|
309
|
-
|
310
|
-
|
311
|
-
if __name__ == "__main__":
|
312
|
-
# Note: Replace the mockup URLs with your actual mockup URLs
|
313
|
-
print("⚠️ Remember to update the mockup URLs in this example!")
|
314
|
-
print(" Replace 'https://mockup.example.com/...' with your actual mockup URLs\n")
|
315
|
-
|
316
|
-
asyncio.run(main())
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|