# Code Generation Critical Fixes Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Fix 6 critical/high-priority code generation bugs in React, CSS, and SwiftUI generators.

**Architecture:** Each fix is isolated to specific functions within the generator files. Changes propagate through the existing `_extract_stroke_data()` / `_extract_effects_data()` data pipeline from `base.py`. No new dependencies needed.

**Tech Stack:** Python 3.10+, pytest (new), Figma node data structures (dicts)

**Important Discovery:** CSS generator (`css_generator.py`) already correctly handles `inset` shadows (line 322-323) and `backdrop-filter` blur (line 333-334). Only React generator needs these fixes. CSS generator still needs dashed border and individual border width fixes.

---

## Task 0: Set Up Test Infrastructure

**Files:**
- Create: `tests/__init__.py`
- Create: `tests/conftest.py`
- Create: `tests/test_generators.py`

**Step 1: Create test directory and conftest**

```bash
mkdir -p tests
```

**Step 2: Create `tests/__init__.py`**

```python
```

**Step 3: Create `tests/conftest.py` with shared fixtures**

```python
"""Shared test fixtures for generator tests."""
import pytest


@pytest.fixture
def node_with_dashed_stroke():
    """Figma node with dashed border stroke."""
    return {
        'type': 'RECTANGLE',
        'absoluteBoundingBox': {'x': 0, 'y': 0, 'width': 200, 'height': 100},
        'fills': [{'type': 'SOLID', 'visible': True, 'color': {'r': 1, 'g': 1, 'b': 1, 'a': 1}, 'opacity': 1}],
        'strokes': [{'type': 'SOLID', 'visible': True, 'color': {'r': 0, 'g': 0, 'b': 0, 'a': 1}, 'opacity': 1}],
        'strokeWeight': 2,
        'strokeAlign': 'INSIDE',
        'strokeDashes': [5, 3],
        'effects': [],
        'children': [],
    }


@pytest.fixture
def node_with_individual_borders():
    """Figma node with different border widths per side."""
    return {
        'type': 'RECTANGLE',
        'absoluteBoundingBox': {'x': 0, 'y': 0, 'width': 200, 'height': 100},
        'fills': [{'type': 'SOLID', 'visible': True, 'color': {'r': 1, 'g': 1, 'b': 1, 'a': 1}, 'opacity': 1}],
        'strokes': [{'type': 'SOLID', 'visible': True, 'color': {'r': 0, 'g': 0, 'b': 0, 'a': 1}, 'opacity': 1}],
        'strokeWeight': 1,
        'strokeTopWeight': 2,
        'strokeRightWeight': 0,
        'strokeBottomWeight': 4,
        'strokeLeftWeight': 0,
        'strokeAlign': 'INSIDE',
        'effects': [],
        'children': [],
    }


@pytest.fixture
def node_with_background_blur():
    """Figma node with BACKGROUND_BLUR effect."""
    return {
        'type': 'RECTANGLE',
        'absoluteBoundingBox': {'x': 0, 'y': 0, 'width': 200, 'height': 100},
        'fills': [{'type': 'SOLID', 'visible': True, 'color': {'r': 1, 'g': 1, 'b': 1, 'a': 0.5}, 'opacity': 0.5}],
        'strokes': [],
        'effects': [{'type': 'BACKGROUND_BLUR', 'visible': True, 'radius': 10}],
        'children': [],
    }


@pytest.fixture
def node_with_inner_shadow():
    """Figma node with INNER_SHADOW effect."""
    return {
        'type': 'RECTANGLE',
        'absoluteBoundingBox': {'x': 0, 'y': 0, 'width': 200, 'height': 100},
        'fills': [{'type': 'SOLID', 'visible': True, 'color': {'r': 1, 'g': 1, 'b': 1, 'a': 1}, 'opacity': 1}],
        'strokes': [],
        'effects': [{
            'type': 'INNER_SHADOW', 'visible': True, 'radius': 4, 'spread': 0,
            'color': {'r': 0, 'g': 0, 'b': 0, 'a': 0.25},
            'offset': {'x': 0, 'y': 2}
        }],
        'children': [],
    }


@pytest.fixture
def node_with_radial_gradient():
    """Figma node with RADIAL gradient fill (300x150 dimensions)."""
    return {
        'type': 'RECTANGLE',
        'absoluteBoundingBox': {'x': 0, 'y': 0, 'width': 300, 'height': 150},
        'fills': [{
            'type': 'GRADIENT_RADIAL', 'visible': True, 'opacity': 1,
            'gradientStops': [
                {'color': {'r': 1, 'g': 0, 'b': 0, 'a': 1}, 'position': 0},
                {'color': {'r': 0, 'g': 0, 'b': 1, 'a': 1}, 'position': 1},
            ],
            'gradientHandlePositions': [
                {'x': 0.5, 'y': 0.5},
                {'x': 1.0, 'y': 0.5},
                {'x': 0.5, 'y': 1.0},
            ]
        }],
        'strokes': [],
        'effects': [],
        'children': [],
    }
```

**Step 4: Verify pytest discovers tests**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/ --collect-only`
Expected: 0 tests collected (no test functions yet), no errors

**Step 5: Commit**

```bash
git add tests/__init__.py tests/conftest.py
git commit -m "test: add test infrastructure and shared fixtures for generator tests"
```

---

## Task 1: Unify MAX_CHILDREN_LIMIT Constants

**Problem:** `figma_mcp.py:134` defines `MAX_CHILDREN_LIMIT = 20` and `MAX_NATIVE_CHILDREN_LIMIT = 10`, while `generators/base.py:20` defines `MAX_CHILDREN_LIMIT = 30` and `MAX_NATIVE_CHILDREN_LIMIT = 20`. The React generator comment on line 471 says "Limit to 20" but uses 30 from base.py.

**Files:**
- Modify: `generators/base.py:20-22`
- Modify: `figma_mcp.py:133-135`
- Test: `tests/test_generators.py`

**Step 1: Write the failing test**

Add to `tests/test_generators.py`:

```python
"""Tests for code generator fixes."""
from generators.base import MAX_CHILDREN_LIMIT, MAX_NATIVE_CHILDREN_LIMIT


class TestChildLimitsConsistency:
    """Verify child limits are consistent across modules."""

    def test_web_child_limit_is_20(self):
        assert MAX_CHILDREN_LIMIT == 20

    def test_native_child_limit_is_15(self):
        assert MAX_NATIVE_CHILDREN_LIMIT == 15

    def test_figma_mcp_imports_from_base(self):
        """figma_mcp.py should import limits from base.py, not define its own."""
        import importlib
        import inspect
        spec = importlib.util.spec_from_file_location(
            "figma_mcp_check",
            "figma_mcp.py"
        )
        source = inspect.getsource(importlib.util.module_from_spec(spec)) if spec else ''
        # Alternative: just read the file
        with open('figma_mcp.py', 'r') as f:
            source = f.read()
        # Should NOT have standalone MAX_CHILDREN_LIMIT = <number> assignment
        import re
        standalone_defs = re.findall(r'^MAX_CHILDREN_LIMIT\s*=\s*\d+', source, re.MULTILINE)
        assert len(standalone_defs) == 0, f"figma_mcp.py still defines its own MAX_CHILDREN_LIMIT: {standalone_defs}"
```

**Step 2: Run test to verify it fails**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestChildLimitsConsistency -v`
Expected: FAIL (base.py has 30, not 20; figma_mcp.py defines its own constants)

**Step 3: Fix `generators/base.py` - update constants**

Change lines 20-21 in `generators/base.py`:
```python
# Before:
MAX_CHILDREN_LIMIT = 30       # React/Vue/CSS child limit per container
MAX_NATIVE_CHILDREN_LIMIT = 20  # SwiftUI/Kotlin child limit

# After:
MAX_CHILDREN_LIMIT = 20       # React/Vue/CSS child limit per container
MAX_NATIVE_CHILDREN_LIMIT = 15  # SwiftUI/Kotlin child limit
```

**Step 4: Fix `figma_mcp.py` - import from base.py instead of defining locally**

Remove lines 133-135 in `figma_mcp.py`:
```python
# Remove these lines:
MAX_CHILDREN_LIMIT = 20
MAX_NATIVE_CHILDREN_LIMIT = 10  # Limit for SwiftUI/Kotlin to avoid excessive code
```

Add import at the top of `figma_mcp.py` (near other generator imports):
```python
from generators.base import MAX_CHILDREN_LIMIT, MAX_NATIVE_CHILDREN_LIMIT
```

**Step 5: Fix React generator comment**

Change line 471 in `react_generator.py`:
```python
# Before:
for child in children[:MAX_CHILDREN_LIMIT]:  # Limit to 20 children for safety

# After:
for child in children[:MAX_CHILDREN_LIMIT]:  # Safety limit
```

**Step 6: Run tests to verify they pass**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestChildLimitsConsistency -v`
Expected: PASS

**Step 7: Commit**

```bash
git add generators/base.py figma_mcp.py generators/react_generator.py tests/test_generators.py
git commit -m "fix: unify MAX_CHILDREN_LIMIT constants - single source in base.py"
```

---

## Task 2: React/CSS - Dashed Border + Individual Border Widths

**Problem:**
1. `react_generator.py:318-324` always renders `border-[Xpx]` without checking `strokeDashes`.
2. `css_generator.py:251` always renders `border: Xpx solid color` - never `dashed`.
3. Both generators ignore `individualWeights` (top/right/bottom/left) from `_extract_stroke_data()`.

**Data available:** `_extract_stroke_data()` (base.py:698) already extracts:
- `result['dashes']` = `node.get('strokeDashes', [])` (line 746)
- `result['individualWeights']` = `{top: X, right: X, bottom: X, left: X}` (lines 733-750)

**Files:**
- Modify: `generators/react_generator.py:99-107, 318-324`
- Modify: `generators/css_generator.py:244-251`
- Test: `tests/test_generators.py`

**Step 1: Write failing tests**

Add to `tests/test_generators.py`:

```python
from generators.react_generator import generate_react_code
from generators.css_generator import generate_css_code


class TestDashedBorder:
    """Verify dashed borders are rendered correctly."""

    def test_react_dashed_border(self, node_with_dashed_stroke):
        code = generate_react_code(node_with_dashed_stroke, 'DashedBox', use_tailwind=True)
        assert 'dashed' in code.lower(), "React should render dashed border style"

    def test_react_solid_border_no_dashes(self):
        node = {
            'type': 'RECTANGLE',
            'absoluteBoundingBox': {'x': 0, 'y': 0, 'width': 200, 'height': 100},
            'fills': [],
            'strokes': [{'type': 'SOLID', 'visible': True, 'color': {'r': 0, 'g': 0, 'b': 0, 'a': 1}, 'opacity': 1}],
            'strokeWeight': 1,
            'strokeAlign': 'INSIDE',
            'effects': [],
            'children': [],
        }
        code = generate_react_code(node, 'SolidBox', use_tailwind=True)
        assert 'border-' in code
        assert 'dashed' not in code.lower()

    def test_css_dashed_border(self, node_with_dashed_stroke):
        code = generate_css_code(node_with_dashed_stroke, 'dashed-box')
        assert 'dashed' in code.lower(), "CSS should render border-style: dashed"

    def test_css_solid_border_no_dashes(self):
        node = {
            'type': 'RECTANGLE',
            'absoluteBoundingBox': {'x': 0, 'y': 0, 'width': 200, 'height': 100},
            'fills': [],
            'strokes': [{'type': 'SOLID', 'visible': True, 'color': {'r': 0, 'g': 0, 'b': 0, 'a': 1}, 'opacity': 1}],
            'strokeWeight': 1,
            'strokeAlign': 'INSIDE',
            'effects': [],
            'children': [],
        }
        code = generate_css_code(node, 'solid-box')
        assert 'solid' in code.lower()


class TestIndividualBorderWidths:
    """Verify individual border widths are used when present."""

    def test_react_individual_borders(self, node_with_individual_borders):
        code = generate_react_code(node_with_individual_borders, 'IndBorders', use_tailwind=True)
        # Should have individual border widths, not uniform
        assert 'border-t-' in code or 'borderTopWidth' in code, "React should render individual top border"
        assert 'border-b-' in code or 'borderBottomWidth' in code, "React should render individual bottom border"

    def test_css_individual_borders(self, node_with_individual_borders):
        code = generate_css_code(node_with_individual_borders, 'ind-borders')
        assert 'border-top-width' in code or 'border-top:' in code, "CSS should render individual top border"
        assert 'border-bottom-width' in code or 'border-bottom:' in code, "CSS should render individual bottom border"
```

**Step 2: Run tests to verify they fail**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestDashedBorder tests/test_generators.py::TestIndividualBorderWidths -v`
Expected: FAIL (dashed not rendered, individual borders ignored)

**Step 3: Fix React generator - extract dashes and individual weights**

In `react_generator.py`, modify the stroke extraction section (around lines 99-107). After extracting `stroke_color`, `stroke_weight`, `stroke_align`, also extract:

```python
    # Strokes (comprehensive)
    stroke_data = _extract_stroke_data(node)
    stroke_color = ''
    stroke_weight = stroke_data['weight'] if stroke_data else 0
    stroke_align = stroke_data['align'] if stroke_data else 'INSIDE'
    stroke_dashes = stroke_data['dashes'] if stroke_data else []
    stroke_individual = stroke_data.get('individualWeights', {}) if stroke_data else {}
    if stroke_data and stroke_data['colors']:
        first_stroke = stroke_data['colors'][0]
        if first_stroke.get('type') == 'SOLID':
            stroke_color = first_stroke.get('color', '')
```

Then modify the Tailwind rendering section (around lines 318-324):

```python
            # Strokes
            if stroke_color and stroke_weight:
                has_dashes = bool(stroke_dashes)
                if stroke_individual:
                    # Individual border widths
                    for side, tw_prefix in [('top', 'border-t'), ('right', 'border-r'), ('bottom', 'border-b'), ('left', 'border-l')]:
                        w = stroke_individual.get(side, 0)
                        if w:
                            classes.append(f'{tw_prefix}-[{w}px]')
                else:
                    classes.append(f'border-[{stroke_weight}px]')
                classes.append(f'border-[{stroke_color}]')
                if has_dashes:
                    inline_styles.append("borderStyle: 'dashed'")
                # Border position (only INSIDE is default in CSS)
                if stroke_align == 'OUTSIDE':
                    inline_styles.append("boxSizing: 'content-box'")
```

**Step 4: Fix CSS generator - dashes and individual weights**

In `css_generator.py`, modify the stroke section (around lines 244-251):

```python
    # Strokes (comprehensive)
    stroke_data = _extract_stroke_data(node)
    stroke_css = ''
    if stroke_data and stroke_data['colors']:
        first_stroke = stroke_data['colors'][0]
        if first_stroke.get('type') == 'SOLID':
            stroke_color = first_stroke.get('color', '')
            stroke_weight = stroke_data['weight']
            stroke_dashes = stroke_data.get('dashes', [])
            stroke_individual = stroke_data.get('individualWeights', {})
            border_style = 'dashed' if stroke_dashes else 'solid'

            if stroke_individual:
                # Individual border widths per side
                parts = []
                for side in ['top', 'right', 'bottom', 'left']:
                    w = stroke_individual.get(side, 0)
                    if w:
                        parts.append(f"border-{side}: {w}px {border_style} {stroke_color};")
                stroke_css = '\n  '.join(parts)
            else:
                stroke_css = f"border: {stroke_weight}px {border_style} {stroke_color};"
```

**Step 5: Run tests to verify they pass**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestDashedBorder tests/test_generators.py::TestIndividualBorderWidths -v`
Expected: PASS

**Step 6: Commit**

```bash
git add generators/react_generator.py generators/css_generator.py tests/test_generators.py
git commit -m "fix: render dashed borders and individual border widths in React/CSS generators"
```

---

## Task 3: React - Background Blur Uses Wrong CSS Property

**Problem:** `react_generator.py:121-126` produces the same `blur_css` for both `LAYER_BLUR` and `BACKGROUND_BLUR`. Then line 332 always renders it as `filter:`. `BACKGROUND_BLUR` should use `backdrop-filter:` + semi-transparent background.

**Note:** CSS generator already handles this correctly (line 333-334).

**Files:**
- Modify: `generators/react_generator.py:121-126, 330-332`
- Test: `tests/test_generators.py`

**Step 1: Write failing test**

Add to `tests/test_generators.py`:

```python
class TestBackdropBlur:
    """Verify BACKGROUND_BLUR uses backdrop-filter, not filter."""

    def test_react_background_blur_uses_backdrop_filter(self, node_with_background_blur):
        code = generate_react_code(node_with_background_blur, 'BlurBox', use_tailwind=True)
        assert 'backdropFilter' in code or 'backdrop-blur' in code, \
            "BACKGROUND_BLUR should use backdropFilter, not filter"
        assert "filter: 'blur" not in code, \
            "BACKGROUND_BLUR should NOT use filter property"

    def test_react_layer_blur_uses_filter(self):
        node = {
            'type': 'RECTANGLE',
            'absoluteBoundingBox': {'x': 0, 'y': 0, 'width': 200, 'height': 100},
            'fills': [],
            'strokes': [],
            'effects': [{'type': 'LAYER_BLUR', 'visible': True, 'radius': 8}],
            'children': [],
        }
        code = generate_react_code(node, 'LayerBlurBox', use_tailwind=True)
        assert "filter:" in code, "LAYER_BLUR should use filter property"
```

**Step 2: Run test to verify it fails**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestBackdropBlur -v`
Expected: FAIL (BACKGROUND_BLUR produces `filter:` instead of `backdropFilter:`)

**Step 3: Fix React generator - separate blur types**

Replace the blur extraction section (lines 121-126) with:

```python
    # Track blur types separately
    layer_blur_css = ''
    backdrop_blur_css = ''
    if effects_data['blurs']:
        for blur in effects_data['blurs']:
            if blur.get('type') == 'LAYER_BLUR':
                layer_blur_css = f"blur({int(blur.get('radius', 0))}px)"
            elif blur.get('type') == 'BACKGROUND_BLUR':
                backdrop_blur_css = f"blur({int(blur.get('radius', 0))}px)"
```

Remove the old `blur_css` variable usage. Then replace the blur rendering section (lines 330-332) with:

```python
            # Blur filters
            if layer_blur_css:
                inline_styles.append(f"filter: '{layer_blur_css}'")
            if backdrop_blur_css:
                inline_styles.append(f"backdropFilter: '{backdrop_blur_css}'")
```

Also ensure no leftover references to `blur_css` variable exist in the function.

**Step 4: Run test to verify it passes**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestBackdropBlur -v`
Expected: PASS

**Step 5: Commit**

```bash
git add generators/react_generator.py tests/test_generators.py
git commit -m "fix: React BACKGROUND_BLUR now uses backdropFilter instead of filter"
```

---

## Task 4: React - Inner Shadow Missing `inset` Keyword

**Problem:** `react_generator.py:113-119` builds shadow CSS without checking shadow type. `INNER_SHADOW` needs the `inset` keyword prefix in `box-shadow`. CSS generator already handles this correctly (line 322-323).

**Files:**
- Modify: `generators/react_generator.py:113-120`
- Test: `tests/test_generators.py`

**Step 1: Write failing test**

Add to `tests/test_generators.py`:

```python
class TestInnerShadow:
    """Verify INNER_SHADOW includes inset keyword in CSS."""

    def test_react_inner_shadow_has_inset(self, node_with_inner_shadow):
        code = generate_react_code(node_with_inner_shadow, 'InsetBox', use_tailwind=True)
        assert 'inset' in code.lower(), "INNER_SHADOW should include 'inset' keyword"

    def test_react_drop_shadow_no_inset(self):
        node = {
            'type': 'RECTANGLE',
            'absoluteBoundingBox': {'x': 0, 'y': 0, 'width': 200, 'height': 100},
            'fills': [],
            'strokes': [],
            'effects': [{
                'type': 'DROP_SHADOW', 'visible': True, 'radius': 4, 'spread': 0,
                'color': {'r': 0, 'g': 0, 'b': 0, 'a': 0.25},
                'offset': {'x': 0, 'y': 2}
            }],
            'children': [],
        }
        code = generate_react_code(node, 'DropBox', use_tailwind=True)
        assert 'inset' not in code.lower(), "DROP_SHADOW should NOT include 'inset'"

    def test_react_mixed_shadows(self):
        """Node with both DROP_SHADOW and INNER_SHADOW."""
        node = {
            'type': 'RECTANGLE',
            'absoluteBoundingBox': {'x': 0, 'y': 0, 'width': 200, 'height': 100},
            'fills': [],
            'strokes': [],
            'effects': [
                {
                    'type': 'DROP_SHADOW', 'visible': True, 'radius': 4, 'spread': 0,
                    'color': {'r': 0, 'g': 0, 'b': 0, 'a': 0.1},
                    'offset': {'x': 0, 'y': 2}
                },
                {
                    'type': 'INNER_SHADOW', 'visible': True, 'radius': 2, 'spread': 0,
                    'color': {'r': 0, 'g': 0, 'b': 0, 'a': 0.2},
                    'offset': {'x': 0, 'y': 1}
                },
            ],
            'children': [],
        }
        code = generate_react_code(node, 'MixedBox', use_tailwind=True)
        # Should have inset for inner, no inset for drop
        assert 'inset' in code.lower(), "Mixed shadows should include 'inset' for INNER_SHADOW"
```

**Step 2: Run test to verify it fails**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestInnerShadow -v`
Expected: FAIL (no `inset` keyword in shadow CSS)

**Step 3: Fix React generator - add shadow type check**

Replace the shadow section (lines 113-120) with:

```python
    if effects_data['shadows']:
        shadow_parts = []
        for shadow in effects_data['shadows']:
            offset = shadow.get('offset', {'x': 0, 'y': 0})
            shadow_type = shadow.get('type', 'DROP_SHADOW')
            inset_prefix = 'inset ' if shadow_type == 'INNER_SHADOW' else ''
            shadow_parts.append(
                f"{inset_prefix}{int(offset.get('x', 0))}px {int(offset.get('y', 0))}px {int(shadow.get('radius', 0))}px {int(shadow.get('spread', 0))}px {shadow.get('color', '#000')}"
            )
        shadow_css = ', '.join(shadow_parts)
```

**Step 4: Run test to verify it passes**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestInnerShadow -v`
Expected: PASS

**Step 5: Commit**

```bash
git add generators/react_generator.py tests/test_generators.py
git commit -m "fix: React INNER_SHADOW now includes 'inset' keyword in box-shadow"
```

---

## Task 5: SwiftUI - Radial Gradient Hardcoded Radius

**Problem:** `swiftui_generator.py:137` always uses `endRadius: 200` for radial gradients. Should scale based on node dimensions: `max(width, height) / 2`.

**Files:**
- Modify: `generators/swiftui_generator.py:118-152`
- Test: `tests/test_generators.py`

**Step 1: Write failing test**

Add to `tests/test_generators.py`:

```python
from generators.base import parse_fills, GradientDef


class TestRadialGradientRadius:
    """Verify radial gradient endRadius scales with dimensions."""

    def test_swiftui_radial_gradient_not_hardcoded_200(self, node_with_radial_gradient):
        """For a 300x150 node, endRadius should be 150 (max/2), not 200."""
        from generators.swiftui_generator import _gradient_to_swiftui
        fills = parse_fills(node_with_radial_gradient)
        gradient = fills[0].gradient
        assert gradient is not None
        assert gradient.type == 'RADIAL'

        # Call with node dimensions
        code, _ = _gradient_to_swiftui(gradient, node_width=300, node_height=150)
        assert 'endRadius: 200' not in code, "endRadius should not be hardcoded to 200"
        assert 'endRadius: 150' in code, "endRadius should be max(300, 150) / 2 = 150"

    def test_swiftui_radial_gradient_square(self):
        """For a 100x100 node, endRadius should be 50."""
        from generators.swiftui_generator import _gradient_to_swiftui
        from generators.base import ColorValue, GradientStop, GradientDef
        gradient = GradientDef(
            type='RADIAL',
            stops=[
                GradientStop(color=ColorValue(r=1, g=0, b=0), position=0),
                GradientStop(color=ColorValue(r=0, g=0, b=1), position=1),
            ]
        )
        code, _ = _gradient_to_swiftui(gradient, node_width=100, node_height=100)
        assert 'endRadius: 50' in code

    def test_swiftui_radial_gradient_default_fallback(self):
        """When no dimensions provided, use reasonable default."""
        from generators.swiftui_generator import _gradient_to_swiftui
        from generators.base import ColorValue, GradientStop, GradientDef
        gradient = GradientDef(
            type='RADIAL',
            stops=[
                GradientStop(color=ColorValue(r=1, g=0, b=0), position=0),
                GradientStop(color=ColorValue(r=0, g=0, b=1), position=1),
            ]
        )
        code, _ = _gradient_to_swiftui(gradient)  # No dimensions
        assert 'endRadius:' in code
        # Default: use 200 as before (backward compat)
        assert 'endRadius: 200' in code
```

**Step 2: Run test to verify it fails**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestRadialGradientRadius -v`
Expected: FAIL (`_gradient_to_swiftui` doesn't accept `node_width`/`node_height` params)

**Step 3: Fix SwiftUI generator - add dimension-based radius**

Modify `_gradient_to_swiftui` function signature and radial gradient logic:

```python
def _gradient_to_swiftui(gradient: GradientDef, node_width: float = 0, node_height: float = 0) -> tuple[str, str]:
    """Convert GradientDef to SwiftUI gradient code.
    Returns (gradient_code, gradient_variable_definition).
    """
    stops_code = []
    for stop in gradient.stops:
        c = stop.color
        color = f"Color(red: {c.r:.3f}, green: {c.g:.3f}, blue: {c.b:.3f})"
        if c.a < 1:
            color += f".opacity({c.a:.2f})"
        stops_code.append(f".init(color: {color}, location: {stop.position:.4f})")

    stops_str = ', '.join(stops_code)

    if gradient.type == 'LINEAR':
        start, end = _gradient_direction_swiftui(gradient.handle_positions)
        code = f"LinearGradient(stops: [{stops_str}], startPoint: {start}, endPoint: {end})"

    elif gradient.type == 'RADIAL':
        end_radius = int(max(node_width, node_height) / 2) if (node_width and node_height) else 200
        code = f"RadialGradient(stops: [{stops_str}], center: .center, startRadius: 0, endRadius: {end_radius})"

    elif gradient.type == 'ANGULAR':
        code = f"AngularGradient(stops: [{stops_str}], center: .center)"

    elif gradient.type == 'DIAMOND':
        end_radius = int(max(node_width, node_height) / 2) if (node_width and node_height) else 200
        code = f"RadialGradient(stops: [{stops_str}], center: .center, startRadius: 0, endRadius: {end_radius})"

    else:
        return '', ''

    if gradient.opacity < 1:
        code += f".opacity({gradient.opacity:.2f})"

    return code, ''
```

**Step 4: Update all callers to pass node dimensions where available**

The callers need to pass `node_width` and `node_height` when they have access to the node. Check each call site:

1. `_fill_layer_to_swiftui` (line 98) - receives `layer`, not `node`. Add optional params:

```python
def _fill_layer_to_swiftui(layer: FillLayer, node_width: float = 0, node_height: float = 0) -> tuple[str, str]:
```

And pass through:
```python
    elif layer.gradient:
        return _gradient_to_swiftui(layer.gradient, node_width, node_height)
```

2. Find the callers of `_fill_layer_to_swiftui` and `_gradient_to_swiftui` that have access to node bbox, and pass dimensions through. For callers that don't have node dimensions, the default (200) will be used as fallback.

**Step 5: Run tests to verify they pass**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestRadialGradientRadius -v`
Expected: PASS

**Step 6: Commit**

```bash
git add generators/swiftui_generator.py tests/test_generators.py
git commit -m "fix: SwiftUI radial gradient endRadius scales with node dimensions"
```

---

## Task 6: base.py - Duplicate Icon Key, hex/rgba, Hardcoded Pi

**Problem:**
1. Line 95-96: `'trending'` key defined twice in `ICON_NAME_MAP`. Second (`flame`) overwrites first (`chart.line.uptrend.xyaxis`).
2. Line 208-212: `ColorValue.hex` property returns `rgba()` string when alpha < 1 (misleading name).
3. Line 920: Uses `180 / 3.14159265359` instead of `math.degrees()` or `math.pi`.

**Files:**
- Modify: `generators/base.py:95-96, 200-212, 920`
- Test: `tests/test_generators.py`

**Step 1: Write failing tests**

Add to `tests/test_generators.py`:

```python
from generators.base import ColorValue, ICON_NAME_MAP
import math


class TestIconNameMapNoDuplicates:
    """Verify no duplicate keys in ICON_NAME_MAP."""

    def test_trending_maps_to_chart(self):
        """'trending' should map to chart icon, 'fire'/'hot' should map to flame."""
        assert ICON_NAME_MAP['trending'] == 'chart.line.uptrend.xyaxis'
        assert ICON_NAME_MAP['fire'] == 'flame'
        assert ICON_NAME_MAP['hot'] == 'flame'


class TestColorValueHex:
    """Verify ColorValue.hex returns proper hex string."""

    def test_opaque_color_returns_hex(self):
        c = ColorValue(r=1.0, g=0.0, b=0.0, a=1.0)
        assert c.hex == '#ff0000'

    def test_transparent_color_returns_8char_hex(self):
        """Alpha < 1 should return #RRGGBBAA format, not rgba()."""
        c = ColorValue(r=1.0, g=0.0, b=0.0, a=0.5)
        result = c.hex
        assert result.startswith('#'), f"hex should return # prefix, got: {result}"
        assert 'rgba' not in result, f"hex should not return rgba(), got: {result}"

    def test_transparent_color_rgba_property(self):
        """rgba property should return rgba() string."""
        c = ColorValue(r=1.0, g=0.0, b=0.0, a=0.5)
        result = c.rgba
        assert result.startswith('rgba('), f"rgba should return rgba() string, got: {result}"


class TestHardcodedPi:
    """Verify math.pi is used instead of hardcoded value."""

    def test_no_hardcoded_pi_in_base(self):
        with open('generators/base.py', 'r') as f:
            source = f.read()
        assert '3.14159265359' not in source, "Should use math.pi instead of hardcoded value"
```

**Step 2: Run tests to verify they fail**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestIconNameMapNoDuplicates tests/test_generators.py::TestColorValueHex tests/test_generators.py::TestHardcodedPi -v`
Expected: FAIL (trending→flame, hex returns rgba(), hardcoded pi)

**Step 3: Fix duplicate icon key**

In `generators/base.py` lines 95-96, change:

```python
# Before:
    'trending': 'chart.line.uptrend.xyaxis',
    'fire': 'flame', 'hot': 'flame', 'trending': 'flame',

# After:
    'trending': 'chart.line.uptrend.xyaxis',
    'fire': 'flame', 'hot': 'flame',
```

**Step 4: Fix ColorValue.hex to return proper hex**

Replace `ColorValue.hex` property and add `rgba` property:

```python
    @property
    def hex(self) -> str:
        ri, gi, bi = int(self.r * 255), int(self.g * 255), int(self.b * 255)
        if self.a < 1:
            ai = int(self.a * 255)
            return f"#{ri:02x}{gi:02x}{bi:02x}{ai:02x}"
        return f"#{ri:02x}{gi:02x}{bi:02x}"

    @property
    def rgba(self) -> str:
        ri, gi, bi = int(self.r * 255), int(self.g * 255), int(self.b * 255)
        if self.a < 1:
            return f"rgba({ri}, {gi}, {bi}, {self.a:.2f})"
        return f"rgb({ri}, {gi}, {bi})"
```

**Important:** After this change, check if any generator code that calls `.hex` expects `rgba()` format when alpha < 1. Search for `.hex` usage and verify they still work with 8-char hex.

**Step 5: Fix hardcoded pi**

In `generators/base.py` line 920, change:

```python
# Before:
        angle_deg = -rotation * (180 / 3.14159265359)

# After:
        angle_deg = -math.degrees(rotation)
```

**Step 6: Verify `.hex` usage across generators doesn't break**

Search all generators for `.hex` usage. If any CSS/React generator passes `.hex` result directly into CSS where `rgba()` was expected, those need to be updated to use `.rgba` instead.

Key places to check:
- `react_generator.py` - stroke colors use `stroke_data['colors'][0].get('color', '')` which comes from `rgba_to_hex()` in base.py (different function, not affected)
- `css_generator.py` - same pattern
- `swiftui_generator.py` - uses `ColorValue` directly with `.r`, `.g`, `.b` (not affected)

Note: The `rgba_to_hex()` standalone function in base.py is separate from `ColorValue.hex`. It's the `ColorValue.hex` property that is misleading. The standalone `rgba_to_hex()` function is used for stroke/shadow colors and may also need checking - but it's a separate function that returns hex strings correctly.

**Step 7: Run tests to verify they pass**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/test_generators.py::TestIconNameMapNoDuplicates tests/test_generators.py::TestColorValueHex tests/test_generators.py::TestHardcodedPi -v`
Expected: PASS

**Step 8: Run ALL tests to verify nothing is broken**

Run: `cd /Users/yusufdemirkoparan/Projects/pixelbyte-figma-mcp && python -m pytest tests/ -v`
Expected: ALL PASS

**Step 9: Commit**

```bash
git add generators/base.py tests/test_generators.py
git commit -m "fix: remove duplicate icon key, fix ColorValue.hex to return proper hex, use math.pi"
```

---

## Summary of Changes

| Task | File(s) | Issue | Status |
|------|---------|-------|--------|
| 0 | tests/ | Test infrastructure | New |
| 1 | base.py, figma_mcp.py | Inconsistent child limits | Critical |
| 2 | react_generator.py, css_generator.py | Dashed border + individual widths | Critical |
| 3 | react_generator.py | Background blur wrong CSS property | Critical |
| 4 | react_generator.py | Inner shadow missing inset | Critical |
| 5 | swiftui_generator.py | Radial gradient hardcoded radius | High |
| 6 | base.py | Duplicate icon, hex/rgba, hardcoded pi | Medium |

**Total commits:** 7 (1 infra + 6 fixes)

**Risk notes:**
- Task 6 `ColorValue.hex` change may break downstream code that expects `rgba()` format. After implementing, run a manual test with a Figma file that has semi-transparent colors and verify the output still looks correct.
- Task 1 child limit change from 30→20 means fewer children rendered. This is intentional for output size, but verify with a complex design.
