# Component Generation Guide (generic)

> Lifted out of `core/multi-agent/SKILL.md`, where it was loaded on every
> run of every mode. It applies only to a task that generates a UI
> component from a design, so it now loads when that path is taken.
> Component dispatch itself is in `component-dispatch.md`.

When the task involves creating a SwiftUI component (any project, not just figma), follow this architecture:

### Component Architecture: Configuration / View / Modifiers

Every component produces up to 5 files:

| File | When | Content |
|------|------|---------|
| `{Name}Configuration.swift` | Complex (>3 props) | Pure value type, all declarative properties |
| `{Name}View.swift` | Always | SwiftUI view, renders Configuration |
| `{Name}+Modifiers.swift` | Complex | Fluent modifier API chain |
| `{Name}Preview.swift` | Always | All meaningful variant previews |
| `{Name}Tests.swift` | Always | ViewInspector + Unit tests |

### Configuration Purity Rule

Configuration structs hold ONLY declarative, value-type properties:

```swift
// ✅ CORRECT  -  pure value type
struct ButtonConfiguration {
    var title: String = ""
    var style: ButtonStyle = .primary
    var isEnabled: Bool = true
    var icon: Image? = nil
}

// ❌ WRONG  -  these do NOT belong in Configuration
// Closures → View property
// @Binding → View property
// @State → View property
// AnyView / @ViewBuilder → View generic parameter
```

### View Implementation

```swift
struct ButtonView: View {
    let configuration: ButtonConfiguration
    private var action: (() -> Void)?  // closure lives in View, not Config

    var body: some View {
        Button(action: { action?() }) {
            HStack(spacing: .Spacing.spacing8) {
                if let icon = configuration.icon { icon }
                Text(configuration.title)
                    .typographyStyle(.body1)
            }
        }
        .disabled(!configuration.isEnabled)
    }
}
```

### Modifier Pattern

```swift
extension ButtonView {
    func title(_ value: String) -> ButtonView {
        var config = configuration
        config.title = value
        return ButtonView(configuration: config)
    }
    func style(_ value: ButtonStyle) -> ButtonView {
        var config = configuration
        config.style = value
        return ButtonView(configuration: config)
    }
    func onTap(_ action: @escaping () -> Void) -> ButtonView {
        var view = self
        view.action = action
        return view
    }
}
```

### Simple vs Complex Decision

| Criteria | Simple | Complex |
|----------|--------|---------|
| Properties | ≤3 | >3 |
| Configuration file | No  -  props in View directly | Yes  -  separate struct |
| +Modifiers file | No | Yes |
| View file | All-in-one | Renders Configuration |

### 3-Layer Test Strategy

| Layer | Tool | What It Validates |
|-------|------|-------------------|
| Structural | ViewInspector | Hierarchy, subview existence, applied modifiers |
| Visual | Snapshot Tests | Pixel-correct render: light/dark, RTL/LTR |
| Behavioral | Unit Tests | State changes, closures, configuration mutations |

### Component Checklist (Before Commit)

1. No magic numbers  -  all values are design tokens or named constants
2. Configuration purity  -  no side-effects or view logic in Config
3. Modifier correctness  -  fluent API valid, paired modifiers live in the View
4. Accessibility  -  accessibility label/trait on every interactive element
5. Preview  -  all meaningful variants in the preview
6. Test  -  structural + unit tests written
7. Dark mode  -  renders correctly in both color schemes
8. Dynamic Type  -  text scales at large sizes without breaking the layout

### When a Figma URL Is Provided

If the user provides a Figma URL:
1. Fetch the design data with Figma MCP tools (get_design_context, get_screenshot)
2. Map the design's colors, spacing, and typography onto the project's token system
3. Apply the Configuration/View/Modifiers pattern above
4. Use the screenshot as a reference, do not copy it  -  adapt to the project's existing components

---

