## SwiftUI Component Generation Guide (Generic)

> **MUST: Figma MCP-first (BLOCKING).** If the task references any Figma frame (URL, node ID, or "from the design"), the Dev phase MUST call `mcp__claude_ai_Figma__get_design_context` for every frame BEFORE writing a single UI line. Use the `CodeConnectSnippet` component name verbatim - no sound-alike substitutions. Authentication failure is not a skip path. Full rule, trigger conditions, and gate failure modes: `$HOME/.claude/rules/figma-pipeline.md` "MUST: Figma MCP-first (BLOCKING)". Phase wiring: `$HOME/.claude/multi-agent-refs/phases/phase-3-dev.md` "MUST: Figma MCP-first (BLOCKING pre-step)".

When the task involves creating a SwiftUI component (any project), follow this architecture.
These practices come from the battle-tested Figma-to-SwiftUI pipeline  -  apply them in every iOS project.

### Component Architecture: Configuration / View / Modifiers

| 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                  |

### Simple vs Complex Decision

- **<=3 properties** -> Simple: all props in View directly, no Configuration struct
- **>3 properties** -> Complex: separate Configuration struct + Modifiers extension
- **Variant-driven** (e.g. style enum with 5+ cases) -> Always Complex, even if few props

### Configuration Purity Rules

Configuration is a **pure value type**  -  zero side effects, zero view logic:

```swift
// Configuration: VALUES ONLY
struct ButtonConfiguration {
    var title: String = ""
    var style: ButtonStyle = .primary
    var size: ButtonSize = .medium
    var isEnabled: Bool = true
    var leadingIcon: Image? = nil
}
```

**What should NOT be in Configuration:**

- `@Binding`, `@State`, `@ObservedObject`  -  these belong in View
- Closures (`onTap: () -> Void`)  -  these belong in View
- `@ViewBuilder` content  -  View
- Computed properties  -  logic stays in View
- Any protocol conformance except `Equatable`, `Hashable`, `Sendable`

### Fluent 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)
    }
}
// Usage: ButtonView().title("OK").style(.secondary)
```

### Token Discipline

**Zero magic numbers. Zero raw colors. Zero raw fonts.**

| Bad                            | Good                                             |
| ------------------------------ | ------------------------------------------------ |
| `padding: 16`                  | `.padding(.Spacing.spacing16)` or named constant |
| `Color(hex: "#E31837")`        | `Color.Primary.primary` or semantic token        |
| `.font(.system(size: 14))`     | `.typographyStyle(.body1)` or project typography |
| `cornerRadius: 8`              | `.Radius.radius8` or named constant              |
| `frame(width: 44, height: 44)` | `.Size.size44` or `minTapTarget` constant        |

If the project has no design token system, create named constants at file scope:

```swift
private enum Layout {
    static let horizontalPadding: CGFloat = 16
    static let cornerRadius: CGFloat = 8
    static let iconSize: CGFloat = 24
}
```

### Variant-Driven Implementation

When a component has variants (style, size, state), derive ALL visual differences from the variant enum:

```swift
enum ButtonStyle {
    case primary, secondary, ghost

    var backgroundColor: Color {
        switch self {
        case .primary: return .Primary.primary
        case .secondary: return .Surface.surfaceSecondary
        case .ghost: return .clear
        }
    }
}
```

**Never use `if/else` chains in the View body for variant styling**  -  push it into the enum or configuration.

### Nested Component Handling

If the design contains sub-components (e.g. a Card with an inner Badge):

1. Check if the sub-component already exists in the project  -  **reuse it**
2. If not, decide: inline (simple, <3 props) or extract (reusable, complex)
3. Pass sub-component configuration through parent: `CardConfiguration.badgeConfig: BadgeConfiguration?`

### Accessibility Requirements

Every interactive element MUST have:

- `accessibilityLabel`  -  what it is (e.g. "Submit button")
- `accessibilityHint`  -  what it does (e.g. "Submits the form")  -  only if not obvious from label
- `accessibilityIdentifier`  -  for UI testing (e.g. `"button_submit"`)
- Correct traits: `.isButton`, `.isHeader`, `.isSelected`, `.isToggle`
- Minimum tap target: **44x44pt** (Apple HIG)

```swift
Button(action: onTap) { ... }
    .accessibilityLabel(configuration.accessibilityLabel)
    .accessibilityIdentifier(TestingIdentifiers.submitButton)
```

### Preview Best Practices

Previews must show **all meaningful variants**, not just the default:

```swift
#Preview("Default") { ButtonView() }
#Preview("Secondary") { ButtonView().style(.secondary) }
#Preview("Disabled") { ButtonView().disabled(true) }
#Preview("With Icon") { ButtonView().leadingIcon(Image(systemName: "star")) }
#Preview("RTL") { ButtonView().environment(\.layoutDirection, .rightToLeft) }
#Preview("Dark Mode") { ButtonView().preferredColorScheme(.dark) }
#Preview("Large Text") { ButtonView().dynamicTypeSize(.xxxLarge) }
```

### 3-Layer Test Strategy

| Layer      | Tool           | Validates                                             | Priority                |
| ---------- | -------------- | ----------------------------------------------------- | ----------------------- |
| Structural | ViewInspector  | Hierarchy, subview existence, applied modifiers       | P0  -  always             |
| Visual     | Snapshot Tests | Pixel render: light/dark, RTL/LTR, Dynamic Type       | P1  -  complex components |
| Behavioral | Unit Tests     | State changes, closure calls, configuration mutations | P0  -  always             |

**Test naming:** `test_{scenario}_{expected}` or `test_{whatItDoes}`

```swift
// ViewInspector  -  structure
func test_hasTitle() throws {
    let sut = ButtonView(configuration: .init(title: "OK"))
    let text = try sut.inspect().find(text: "OK")
    XCTAssertNotNil(text)
}

// Unit  -  configuration mutation via modifier
func test_titleModifier_updatesConfiguration() {
    let sut = ButtonView().title("Submit")
    XCTAssertEqual(sut.configuration.title, "Submit")
}

// Snapshot  -  visual regression
func test_snapshot_light_primary() {
    assertSnapshot(of: ButtonView(configuration: .init(title: "OK")),
                   as: .image(layout: .device(config: .iPhone13)))
}
```

### Build Verification

After implementation, **always run build before considering complete**:

- Fix all compiler errors
- Fix all warnings in the component files
- Ensure previews render without crash

### Component Quality Checklist

Before marking a component as done:

1. No magic numbers  -  all values are tokens or named constants
2. Configuration purity  -  no side-effects, closures, or view logic in Config
3. Modifier chain works  -  each modifier returns a new View with updated config
4. Accessibility  -  labels, hints, identifiers, traits, 44pt tap targets
5. Preview coverage  -  all meaningful variants, dark mode, RTL, Dynamic Type
6. Tests  -  structural (ViewInspector) + behavioral (Unit), snapshot if complex
7. Dark mode  -  correct in both color schemes
8. Dynamic Type  -  text scales without layout breaking
9. RTL  -  leading/trailing used, layout mirrors correctly
10. Variant exhaustiveness  -  all enum cases handled, no default catch-all
11. Build passes  -  zero errors, zero warnings on component files
12. Self-documenting  -  code is clear without comments, MARK sections organized

### Compliance Rules (maps to multi-agent-toolkit MCP audit tools)

These rules ensure your code passes `ios_accessibility_audit` and `ios_app_store_audit` without issues. Follow them during development  -  don't wait for audit to catch problems.

#### Accessibility (validated by `ios_accessibility_audit`)

| Rule                                                    | What Audit Checks                                        | How to Pass                                 |
| ------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------- |
| Every interactive element has `accessibilityLabel`      | Missing label on Button, Link, TextField, Toggle, Slider | `.accessibilityLabel("Submit order")`       |
| Every interactive element has `accessibilityIdentifier` | Missing identifier                                       | `.accessibilityIdentifier("button_submit")` |
| Minimum tap target 44x44pt                              | Frame size < 44x44                                       | `.frame(minWidth: 44, minHeight: 44)`       |
| Correct traits                                          | Not checked by tool, but best practice                   | `.accessibilityAddTraits(.isButton)`        |

```swift
// This PASSES audit:
Button(action: onTap) {
    Image(systemName: "xmark")
}
.accessibilityLabel("Close")
.accessibilityIdentifier("button_close")
.frame(minWidth: 44, minHeight: 44)

// This FAILS audit:
Button(action: onTap) {     // no label
    Image(systemName: "xmark")  // no identifier
}                               // no min frame
```

#### Release Compliance (validated by `ios_app_store_audit`)

| Rule                             | What Audit Checks                                 | How to Pass                                                              |
| -------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------ |
| No debug tools in release        | FLEX, Reveal, CocoaDebug, Pulse symbols in binary | `#if DEBUG` around debug imports, never link debug frameworks in Release |
| ATS enforced                     | `NSAllowsArbitraryLoads = true` in Info.plist     | Remove ATS exception or add justification per domain                     |
| Privacy manifest present         | Missing `PrivacyInfo.xcprivacy`                   | Add manifest, declare Required Reason APIs                               |
| Privacy strings for permissions  | Missing `NS*UsageDescription` keys                | Add all needed `NSCameraUsageDescription` etc. in Info.plist             |
| No debug entitlements in release | `get-task-allow = true`                           | Use Release/Distribution profile, not Debug                              |
| Bundle version set               | Missing `CFBundleShortVersionString`              | Always set in Info.plist or build settings                               |

```swift
// Debug tools: guard with #if DEBUG
#if DEBUG
import FLEX
#endif

// App startup:
#if DEBUG
FLEXManager.shared.showExplorer()
#endif
```

### Figma URL Given

If user provides a Figma URL, use Figma MCP tools (get_design_context, get_screenshot) to fetch design data, map to project tokens, and apply Configuration/View/Modifiers pattern.

For figma project specifically: multi-agent reads `.instructions/figma/` SKILL.md files for the full 8-phase pipeline.
