cursorflow 2.4.3__py3-none-any.whl → 2.5.0__py3-none-any.whl

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/cli.py CHANGED
@@ -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 recommendations
438
- recommendations = results.get('recommendations', [])
439
- if recommendations:
440
- console.print(f"💡 Recommendations: {len(recommendations)} improvements suggested")
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 numpy type handling
445
- with open(output, 'w') as f:
446
- def json_serializer(obj):
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=str)
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
- # Format results for Cursor analysis
579
- session_id = f"mockup_comparison_{int(time.time())}"
580
- cursor_results = self.cursor_integration.format_mockup_comparison_results(
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
- with open(report_path, 'w') as f:
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
- with open(report_path, 'w') as f:
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
- "best_viewport_match": max(comparison_results, key=lambda x: x.get("visual_diff", {}).get("similarity_score", 0)),
1230
- "needs_improvement": bool(avg_similarity < 80) # Convert numpy.bool_ to Python bool
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**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cursorflow
3
- Version: 2.4.3
3
+ Version: 2.5.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 iteration with HMR intelligence
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-spacing", "css": ".container { gap: 2rem; }"}
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**
@@ -1,7 +1,7 @@
1
1
  cursorflow/__init__.py,sha256=2V9xzG2tYxVWOTmSw2v9Jdbr7lSrMi_y2SMUMuNZdvw,2990
2
2
  cursorflow/auto_init.py,sha256=dXQaXXiXe4wkUP-jd8fcJ5fYVt7ASdTb47b7SzXymOM,6122
3
3
  cursorflow/auto_updater.py,sha256=oQ12TIMZ6Cm3HF-x9iRWFtvOLkRh-JWPqitS69-4roE,7851
4
- cursorflow/cli.py,sha256=CzvxsXYt-cHImGDgnzO1cVjUE819fX6UrILMTGL4v3U,65084
4
+ cursorflow/cli.py,sha256=Wj4i60rYGZlGaejES7msuEFWODlu_f0DMlVnHzEFSaU,64742
5
5
  cursorflow/install_cursorflow_rules.py,sha256=DsZ0680y9JMuTKFXjdgYtOKIEAjBMsdwL8LmA9WEb5A,11864
6
6
  cursorflow/post_install.py,sha256=WieBiKWG0qBAQpF8iMVWUyb9Fr2Xky9qECTMPrlAbpE,2678
7
7
  cursorflow/updater.py,sha256=SroSQHQi5cYyzcOK_bf-WzmQmE7yeOs8qo3r__j-Z6E,19583
@@ -13,15 +13,16 @@ cursorflow/core/browser_engine.py,sha256=7N9hPOyDrEhLWYgZW2981N9gKlHF6Lbp7D7h0zB
13
13
  cursorflow/core/config_validator.py,sha256=HRtONSOmM0Xxt3-ok3xwnBADRiNnI0nNOMaS2OqOkDk,7286
14
14
  cursorflow/core/css_iterator.py,sha256=whLCIwbHZEWaH1HCbmqhNX5zrh_fL-r3hsxKjYsukcE,16478
15
15
  cursorflow/core/cursor_integration.py,sha256=MAeHjXYeqzaXnhskqkTDB-n5ixIHqapGe93X0lLKhws,67501
16
- cursorflow/core/cursorflow.py,sha256=qLTyLMf1fm2l5yDafalZXbtBHETsDXyJUSyoW4f8L_o,46470
16
+ cursorflow/core/cursorflow.py,sha256=j1LRNiRpUl3gZlB3r-yQ_h0HISNwnhi8-iZa_E0h86o,46111
17
17
  cursorflow/core/error_context_collector.py,sha256=so-aveqwb6_vRDbtKR2ln8_F84aaVIceNi1UHsaVPgc,26196
18
18
  cursorflow/core/error_correlator.py,sha256=g6YaIF2yFXUDrTuWHCyuES6K0696H8LDWmXH1qI8ddA,13367
19
19
  cursorflow/core/event_correlator.py,sha256=lCzXFnii17AQt3rOVOclez_AnjvBCF_2ZlnFG0mIN6c,7808
20
20
  cursorflow/core/file_change_monitor.py,sha256=tl_ZdsGW8LZBTImniD-lqaGgNb9dYjlDsP2XmevE0xk,21258
21
21
  cursorflow/core/hmr_detector.py,sha256=IrGYhXIeTBukffRXF74SW45cB_Im0cmq8RxxjtSz-2w,17431
22
+ cursorflow/core/json_utils.py,sha256=ofqRP1twDSHQx-SERgLTO5y2ZXkoirsEw8OOiJkGh2w,1370
22
23
  cursorflow/core/log_collector.py,sha256=CYh41SLDI_t-hgzJqG3fTrSTDhEPFDktjO_mOQwucnE,15198
23
24
  cursorflow/core/log_monitor.py,sha256=pONMu_JHEnT0T62OA5KRZ4nClzKgNpifPyrfN5w_RM8,6704
24
- cursorflow/core/mockup_comparator.py,sha256=Xg6_LGfPxTAaEf5eVdrBBtq8NVtBhy-C8wd-96w3WwY,65059
25
+ cursorflow/core/mockup_comparator.py,sha256=ttcXdueZz9dwcUGBQ2sft4i66zRdkZkFPu41s6JlEwU,63472
25
26
  cursorflow/core/persistent_session.py,sha256=FsEHj4wKkycmdp6PFRHv3g333Y74yqra0x_qhUTQpik,36075
26
27
  cursorflow/core/report_generator.py,sha256=-vosfyrnfVyWDbAIMlMurl90xOXqBae8d6aLd9sEqiY,10113
27
28
  cursorflow/core/trace_manager.py,sha256=Jj9ultZrL1atiZXfcRVI6ynCnnfqZM-X0_taxt-llJ0,7189
@@ -29,10 +30,10 @@ cursorflow/log_sources/local_file.py,sha256=GVnhsaifIdc41twXwbxRM9-fBeRDsknDpk5I
29
30
  cursorflow/log_sources/ssh_remote.py,sha256=_Kwh0bhRpKgq-0c98oaX2hN6h9cT-wCHlqY5NiWVCoY,8388
30
31
  cursorflow/rules/__init__.py,sha256=gPcA-IkhXj03sl7cvZV0wwo7CtEkcyuKs4y0F5oQbqE,458
31
32
  cursorflow/rules/cursorflow-installation.mdc,sha256=D55pzzDPAVVbE3gAtKPUGoT-2fvB-FI2l6yrTdzUIEo,10208
32
- cursorflow/rules/cursorflow-usage.mdc,sha256=8TUeQ1hq2fTlgPqUBYOVBXrdEN0BR0Xp0d_1L-eUsME,23150
33
- cursorflow-2.4.3.dist-info/licenses/LICENSE,sha256=e4QbjAsj3bW-xgQOvQelr8sGLYDoqc48k6cKgCr_pBU,1080
34
- cursorflow-2.4.3.dist-info/METADATA,sha256=kyrYszlZJXJSSjvt4wdASU4onxYp3vq3MoFVtv49rdQ,14793
35
- cursorflow-2.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
- cursorflow-2.4.3.dist-info/entry_points.txt,sha256=-Ed_n4Uff7wClEtWS-Py6xmQabecB9f0QAOjX0w7ljA,51
37
- cursorflow-2.4.3.dist-info/top_level.txt,sha256=t1UZwRyZP4u-ng2CEcNHmk_ZT4ibQxoihB2IjTF7ovc,11
38
- cursorflow-2.4.3.dist-info/RECORD,,
33
+ cursorflow/rules/cursorflow-usage.mdc,sha256=W56Qydfb4jqSBTrki7cNyFPfOe_b89mzniRtKSrMlz4,24138
34
+ cursorflow-2.5.0.dist-info/licenses/LICENSE,sha256=e4QbjAsj3bW-xgQOvQelr8sGLYDoqc48k6cKgCr_pBU,1080
35
+ cursorflow-2.5.0.dist-info/METADATA,sha256=vr-9bDM-frTvKKRDVPCdekst6mD8oNtxObWj9Y9VaUY,15011
36
+ cursorflow-2.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ cursorflow-2.5.0.dist-info/entry_points.txt,sha256=-Ed_n4Uff7wClEtWS-Py6xmQabecB9f0QAOjX0w7ljA,51
38
+ cursorflow-2.5.0.dist-info/top_level.txt,sha256=t1UZwRyZP4u-ng2CEcNHmk_ZT4ibQxoihB2IjTF7ovc,11
39
+ cursorflow-2.5.0.dist-info/RECORD,,