# LiveComponents End-to-End Creation Guide

Complete guide for creating LiveComponents from scratch - from concept to production deployment.

## Table of Contents

- [Overview](#overview)
- [Quick Start](#quick-start)
- [Step-by-Step Tutorial](#step-by-step-tutorial)
- [Component Architecture](#component-architecture)
- [Advanced Patterns](#advanced-patterns)
- [Testing](#testing)
- [Deployment](#deployment)
- [Troubleshooting](#troubleshooting)

---

## Overview

A **LiveComponent** is a server-rendered, interactive UI component with automatic state synchronization. Components are:

- **Server-rendered**: HTML generated on the server
- **Stateful**: Maintain state across interactions
- **Interactive**: Respond to user actions without full page reloads
- **Framework-compliant**: Readonly classes, Value Objects, Composition

### When to Use LiveComponents

✅ **Use LiveComponents for:**
- Interactive forms with validation
- Real-time dashboards
- Shopping carts
- Search interfaces with filters
- File uploads with progress
- Paginated lists
- Wizards/multi-step forms

❌ **Don't use LiveComponents for:**
- Static content (use regular templates)
- Simple links (use standard HTML)
- High-frequency updates (use WebSockets directly)
- Complex client-side logic (use dedicated JavaScript)

---

## Quick Start

### Minimal Component (5 minutes)

```php
<?php

declare(strict_types=1);

namespace App\Application\LiveComponents\Counter;

use App\Framework\LiveComponents\Attributes\Action;
use App\Framework\LiveComponents\Attributes\LiveComponent;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\LiveComponents\ValueObjects\ComponentId;

// 1. Mark class with #[LiveComponent] attribute
#[LiveComponent('counter')]
// 2. Implement LiveComponentContract
final readonly class CounterComponent implements LiveComponentContract
{
    // 3. Constructor: ComponentId + State
    public function __construct(
        public ComponentId $id,
        public CounterState $state
    ) {}

    // 4. Define actions with #[Action]
    #[Action]
    public function increment(): CounterState
    {
        return $this->state->increment();
    }

    #[Action]
    public function decrement(): CounterState
    {
        return $this->state->decrement();
    }

    // 5. Render method returns template path
    public function render(): string
    {
        return 'components/counter';
    }
}
```

### State Value Object

```php
<?php

declare(strict_types=1);

namespace App\Application\LiveComponents\Counter;

use App\Framework\LiveComponents\Contracts\ComponentData;
use DateTimeImmutable;

// State must implement ComponentData
final readonly class CounterState implements ComponentData
{
    public function __construct(
        public int $count = 0,
        public ?DateTimeImmutable $lastUpdate = null
    ) {}

    // Immutable state updates
    public function increment(): self
    {
        return new self(
            count: $this->count + 1,
            lastUpdate: new DateTimeImmutable()
        );
    }

    public function decrement(): self
    {
        return new self(
            count: max(0, $this->count - 1), // Prevent negative
            lastUpdate: new DateTimeImmutable()
        );
    }

    // Required by ComponentData
    public function toArray(): array
    {
        return [
            'count' => $this->count,
            'lastUpdate' => $this->lastUpdate?->format('Y-m-d H:i:s')
        ];
    }
}
```

### Template

```html
<!-- resources/views/components/counter.view.php -->
<div data-lc-component="counter" data-lc-id="{component_id}">
    <h2>Counter: {count}</h2>

    <button data-lc-action="increment">+1</button>
    <button data-lc-action="decrement">-1</button>

    <if condition="lastUpdate">
        <p>Last update: {lastUpdate}</p>
    </if>
</div>
```

### Usage in Controller

```php
use App\Framework\LiveComponents\ComponentRegistry;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\View\ViewResult;

public function show(ComponentRegistry $registry): ViewResult
{
    $component = $registry->resolve(
        ComponentId::fromString('counter:main'),
        initialData: ['count' => 0]
    );

    return new ViewResult('pages/counter', [
        'component' => $component
    ]);
}
```

**That's it!** Component is now interactive. Actions automatically sync state to the server.

---

## Step-by-Step Tutorial

Let's build a **Shopping Cart Component** from scratch.

### Step 1: Define the Domain

```php
<?php
// src/Application/LiveComponents/ShoppingCart/CartItem.php

namespace App\Application\LiveComponents\ShoppingCart;

use App\Framework\Core\ValueObjects\Price;

final readonly class CartItem
{
    public function __construct(
        public string $productId,
        public string $name,
        public Price $price,
        public int $quantity
    ) {
        if ($quantity < 1) {
            throw new \InvalidArgumentException('Quantity must be at least 1');
        }
    }

    public function getTotal(): Price
    {
        return $this->price->multiply($this->quantity);
    }

    public function withQuantity(int $quantity): self
    {
        return new self(
            $this->productId,
            $this->name,
            $this->price,
            $quantity
        );
    }

    public function toArray(): array
    {
        return [
            'product_id' => $this->productId,
            'name' => $this->name,
            'price' => $this->price->toDecimal(),
            'quantity' => $this->quantity,
            'total' => $this->getTotal()->toDecimal()
        ];
    }
}
```

### Step 2: Create State Value Object

```php
<?php
// src/Application/LiveComponents/ShoppingCart/ShoppingCartState.php

namespace App\Application\LiveComponents\ShoppingCart;

use App\Framework\LiveComponents\Contracts\ComponentData;
use App\Framework\Core\ValueObjects\Price;

final readonly class ShoppingCartState implements ComponentData
{
    /** @param array<CartItem> $items */
    public function __construct(
        public array $items = [],
        public ?string $couponCode = null,
        public int $discountPercentage = 0
    ) {}

    public function addItem(CartItem $item): self
    {
        // Check if item already exists
        foreach ($this->items as $existingItem) {
            if ($existingItem->productId === $item->productId) {
                // Update quantity
                return $this->updateQuantity(
                    $item->productId,
                    $existingItem->quantity + $item->quantity
                );
            }
        }

        // Add new item
        return new self(
            items: [...$this->items, $item],
            couponCode: $this->couponCode,
            discountPercentage: $this->discountPercentage
        );
    }

    public function removeItem(string $productId): self
    {
        return new self(
            items: array_values(array_filter(
                $this->items,
                fn(CartItem $item) => $item->productId !== $productId
            )),
            couponCode: $this->couponCode,
            discountPercentage: $this->discountPercentage
        );
    }

    public function updateQuantity(string $productId, int $quantity): self
    {
        if ($quantity < 1) {
            return $this->removeItem($productId);
        }

        $updatedItems = array_map(
            fn(CartItem $item) => $item->productId === $productId
                ? $item->withQuantity($quantity)
                : $item,
            $this->items
        );

        return new self(
            items: $updatedItems,
            couponCode: $this->couponCode,
            discountPercentage: $this->discountPercentage
        );
    }

    public function applyCoupon(string $couponCode, int $discountPercentage): self
    {
        return new self(
            items: $this->items,
            couponCode: $couponCode,
            discountPercentage: $discountPercentage
        );
    }

    public function getSubtotal(): Price
    {
        $total = 0;
        foreach ($this->items as $item) {
            $total += $item->getTotal()->cents;
        }

        return Price::fromCents($total);
    }

    public function getDiscount(): Price
    {
        if ($this->discountPercentage === 0) {
            return Price::fromCents(0);
        }

        $subtotal = $this->getSubtotal();
        $discountAmount = ($subtotal->cents * $this->discountPercentage) / 100;

        return Price::fromCents((int) $discountAmount);
    }

    public function getTotal(): Price
    {
        return $this->getSubtotal()->subtract($this->getDiscount());
    }

    public function getItemCount(): int
    {
        return array_reduce(
            $this->items,
            fn(int $sum, CartItem $item) => $sum + $item->quantity,
            0
        );
    }

    public function isEmpty(): bool
    {
        return empty($this->items);
    }

    public function toArray(): array
    {
        return [
            'items' => array_map(fn(CartItem $item) => $item->toArray(), $this->items),
            'coupon_code' => $this->couponCode,
            'discount_percentage' => $this->discountPercentage,
            'subtotal' => $this->getSubtotal()->toDecimal(),
            'discount' => $this->getDiscount()->toDecimal(),
            'total' => $this->getTotal()->toDecimal(),
            'item_count' => $this->getItemCount(),
            'is_empty' => $this->isEmpty()
        ];
    }
}
```

### Step 3: Create Component

```php
<?php
// src/Application/LiveComponents/ShoppingCart/ShoppingCartComponent.php

namespace App\Application\LiveComponents\ShoppingCart;

use App\Framework\LiveComponents\Attributes\Action;
use App\Framework\LiveComponents\Attributes\LiveComponent;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentEventDispatcher;
use App\Framework\LiveComponents\ValueObjects\EventPayload;
use App\Framework\Core\ValueObjects\Price;

#[LiveComponent('shopping-cart')]
final readonly class ShoppingCartComponent implements LiveComponentContract
{
    public function __construct(
        public ComponentId $id,
        public ShoppingCartState $state,
        private CouponService $couponService // Injected service
    ) {}

    #[Action(rateLimit: 30, rateLimitWindow: 60)] // 30 updates per minute
    public function addItem(
        string $product_id,
        string $name,
        int $price_cents,
        int $quantity = 1,
        ?ComponentEventDispatcher $events = null
    ): ShoppingCartState {
        $item = new CartItem(
            productId: $product_id,
            name: $name,
            price: Price::fromCents($price_cents),
            quantity: $quantity
        );

        $newState = $this->state->addItem($item);

        // Dispatch event for analytics
        $events?->dispatch('cart:item-added', EventPayload::fromArray([
            'product_id' => $product_id,
            'quantity' => $quantity,
            'total_items' => $newState->getItemCount()
        ]));

        return $newState;
    }

    #[Action]
    public function removeItem(
        string $product_id,
        ?ComponentEventDispatcher $events = null
    ): ShoppingCartState {
        $newState = $this->state->removeItem($product_id);

        $events?->dispatch('cart:item-removed', EventPayload::fromArray([
            'product_id' => $product_id,
            'total_items' => $newState->getItemCount()
        ]));

        return $newState;
    }

    #[Action]
    public function updateQuantity(
        string $product_id,
        int $quantity
    ): ShoppingCartState {
        return $this->state->updateQuantity($product_id, $quantity);
    }

    #[Action(rateLimit: 5, rateLimitWindow: 300)] // 5 coupon attempts per 5 min
    public function applyCoupon(
        string $coupon_code,
        ?ComponentEventDispatcher $events = null
    ): ShoppingCartState {
        // Validate coupon via service
        $coupon = $this->couponService->validate($coupon_code);

        if (!$coupon->isValid()) {
            $events?->dispatch('cart:coupon-invalid', EventPayload::fromArray([
                'coupon_code' => $coupon_code,
                'reason' => $coupon->getInvalidReason()
            ]));

            // Return current state unchanged
            return $this->state;
        }

        $newState = $this->state->applyCoupon(
            $coupon_code,
            $coupon->getDiscountPercentage()
        );

        $events?->dispatch('cart:coupon-applied', EventPayload::fromArray([
            'coupon_code' => $coupon_code,
            'discount_percentage' => $coupon->getDiscountPercentage(),
            'discount_amount' => $newState->getDiscount()->toDecimal()
        ]));

        return $newState;
    }

    #[Action(
        rateLimit: 3,
        rateLimitWindow: 300,
        idempotencyTTL: 3600
    )]
    public function checkout(?ComponentEventDispatcher $events = null): ShoppingCartState
    {
        if ($this->state->isEmpty()) {
            $events?->dispatch('cart:checkout-failed', EventPayload::fromArray([
                'reason' => 'empty_cart'
            ]));

            return $this->state;
        }

        // Process checkout (would integrate with payment service)
        $events?->dispatch('cart:checkout-initiated', EventPayload::fromArray([
            'total' => $this->state->getTotal()->toDecimal(),
            'item_count' => $this->state->getItemCount()
        ]));

        return $this->state;
    }

    public function render(): string
    {
        return 'components/shopping-cart';
    }
}
```

### Step 4: Create Template

```html
<!-- resources/views/components/shopping-cart.view.php -->
<div
    data-lc-component="shopping-cart"
    data-lc-id="{component_id}"
    class="shopping-cart"
>
    <h2>Shopping Cart ({item_count} items)</h2>

    <if condition="is_empty">
        <p class="empty-message">Your cart is empty</p>
    </if>

    <if condition="!is_empty">
        <!-- Cart Items (Fragment for partial updates) -->
        <div data-lc-fragment="cart-items">
            <for items="items" as="item">
                <div class="cart-item">
                    <h3>{item.name}</h3>
                    <p>Price: €{item.price}</p>

                    <div class="quantity-control">
                        <button
                            data-lc-action="updateQuantity"
                            data-lc-params='{"product_id": "{item.product_id}", "quantity": {item.quantity - 1}}'
                        >-</button>

                        <span>{item.quantity}</span>

                        <button
                            data-lc-action="updateQuantity"
                            data-lc-params='{"product_id": "{item.product_id}", "quantity": {item.quantity + 1}}'
                        >+</button>
                    </div>

                    <p>Total: €{item.total}</p>

                    <button
                        data-lc-action="removeItem"
                        data-lc-params='{"product_id": "{item.product_id}"}'
                        class="remove-btn"
                    >Remove</button>
                </div>
            </for>
        </div>

        <!-- Cart Summary (Fragment for partial updates) -->
        <div data-lc-fragment="cart-summary" class="cart-summary">
            <div>
                <span>Subtotal:</span>
                <span>€{subtotal}</span>
            </div>

            <if condition="discount_percentage > 0">
                <div class="discount">
                    <span>Discount ({discount_percentage}%):</span>
                    <span>-€{discount}</span>
                </div>
            </if>

            <div class="total">
                <span>Total:</span>
                <span>€{total}</span>
            </div>
        </div>

        <!-- Coupon Form -->
        <form data-lc-action="applyCoupon" class="coupon-form">
            <input type="text" name="coupon_code" placeholder="Coupon code" />
            <button type="submit">Apply</button>
        </form>

        <!-- Checkout Button -->
        <button
            data-lc-action="checkout"
            class="checkout-btn"
            data-lc-confirm="Proceed to checkout?"
        >
            Checkout (€{total})
        </button>
    </if>
</div>
```

### Step 5: JavaScript Integration (Optional)

```javascript
// resources/js/modules/shopping-cart.js
import { LiveComponent } from './live-component.js';

const cartComponent = new LiveComponent('shopping-cart:main');

// Listen to events
cartComponent.on('cart:item-added', (data) => {
    console.log('Item added:', data.product_id);

    // Show notification
    showNotification(`Added ${data.quantity} item(s) to cart`);

    // Update cart badge
    updateCartBadge(data.total_items);
});

cartComponent.on('cart:coupon-applied', (data) => {
    showNotification(
        `Coupon applied! You save ${data.discount_percentage}%`,
        'success'
    );
});

cartComponent.on('cart:coupon-invalid', (data) => {
    showNotification(
        `Invalid coupon: ${data.reason}`,
        'error'
    );
});

cartComponent.on('cart:checkout-initiated', (data) => {
    // Redirect to checkout page
    window.location.href = `/checkout?total=${data.total}`;
});

// Fragment updates for better performance
cartComponent.on('action:updateQuantity', async (event) => {
    // Only update cart-items and cart-summary fragments
    await cartComponent.executeAction(
        'updateQuantity',
        event.params,
        { fragments: ['cart-items', 'cart-summary'] }
    );
});
```

### Step 6: Testing

```php
<?php
// tests/Feature/LiveComponents/ShoppingCartComponentTest.php

use function Pest\LiveComponents\mountComponent;
use function Pest\LiveComponents\callAction;

describe('ShoppingCartComponent', function () {
    it('starts with empty cart', function () {
        $component = mountComponent('shopping-cart:test');

        expect($component['state'])->toHaveStateKey('is_empty', true);
        expect($component['state'])->toHaveStateKey('item_count', 0);
    });

    it('adds items to cart', function () {
        $component = mountComponent('shopping-cart:test');

        $result = callAction($component, 'addItem', [
            'product_id' => 'product-123',
            'name' => 'Test Product',
            'price_cents' => 1999,
            'quantity' => 2
        ]);

        expect($result['state'])->toHaveStateKey('item_count', 2);
        expect($result['state'])->toHaveStateKey('subtotal', '39.98');
        expect($result['events'])->toHaveDispatchedEvent('cart:item-added');
    });

    it('applies valid coupon', function () {
        $component = mountComponent('shopping-cart:test');

        // Add item first
        $component = callAction($component, 'addItem', [
            'product_id' => 'product-123',
            'name' => 'Test Product',
            'price_cents' => 10000,
            'quantity' => 1
        ]);

        // Apply coupon
        $result = callAction($component, 'applyCoupon', [
            'coupon_code' => 'SAVE20'
        ]);

        expect($result['state'])->toHaveStateKey('discount_percentage', 20);
        expect($result['state'])->toHaveStateKey('discount', '20.00');
        expect($result['state'])->toHaveStateKey('total', '80.00');
        expect($result['events'])->toHaveDispatchedEvent('cart:coupon-applied');
    });

    it('enforces rate limiting on checkout', function () {
        $component = mountComponent('shopping-cart:test');

        // Add item
        $component = callAction($component, 'addItem', [
            'product_id' => 'product-123',
            'name' => 'Test Product',
            'price_cents' => 10000
        ]);

        // Try to checkout 4 times (limit is 3)
        callAction($component, 'checkout');
        callAction($component, 'checkout');
        callAction($component, 'checkout');

        // 4th attempt should be rate limited
        expect(fn() => callAction($component, 'checkout'))
            ->toThrow(RateLimitExceededException::class);
    });
});
```

---

## Component Architecture

### Required Elements

Every LiveComponent must have:

1. **#[LiveComponent] attribute** with unique name
2. **LiveComponentContract implementation**
3. **ComponentId + State in constructor**
4. **State as ComponentData Value Object**
5. **render() method returning template path**

### Optional Elements

- **#[Action] methods** for interactivity
- **Service injection** via constructor
- **Event dispatching** for side effects
- **Pollable interface** for auto-refresh
- **Fragment markers** for partial updates

### Component Lifecycle

```
1. Resolve (ComponentRegistry)
   ├─ Create instance via DI Container
   ├─ Inject ComponentId
   ├─ Inject initial State
   └─ Inject Services

2. Render (LiveComponentRenderer)
   ├─ Call render() for template path
   ├─ Pass state data to template
   ├─ Process template (placeholders, conditionals, loops)
   └─ Return HTML

3. Action Execution (LiveComponentHandler)
   ├─ Validate CSRF token
   ├─ Check rate limits
   ├─ Check idempotency
   ├─ Bind parameters
   ├─ Execute action method
   ├─ Create new State from return value
   ├─ Dispatch events
   └─ Return ComponentUpdate

4. Re-render
   ├─ Create new component instance with new State
   ├─ Render updated HTML
   ├─ Extract fragments (if requested)
   └─ Return response (html/fragments + state + events)
```

---

## Advanced Patterns

### Service Injection

```php
#[LiveComponent('user-profile')]
final readonly class UserProfileComponent implements LiveComponentContract
{
    public function __construct(
        public ComponentId $id,
        public UserProfileState $state,
        // Inject services via constructor
        private UserRepository $userRepository,
        private AvatarService $avatarService,
        private PermissionChecker $permissions
    ) {}

    #[Action]
    public function updateAvatar(UploadedFile $avatar): UserProfileState
    {
        $url = $this->avatarService->upload($avatar);

        return $this->state->withAvatarUrl($url);
    }
}
```

### Event Dispatching

```php
#[Action]
public function completeOrder(
    ?ComponentEventDispatcher $events = null
): OrderState {
    $order = $this->orderService->complete($this->state->orderId);

    // Dispatch domain event
    $events?->dispatch('order:completed', EventPayload::fromArray([
        'order_id' => $order->id,
        'total' => $order->total->toDecimal(),
        'customer_id' => $order->customerId
    ]));

    // Other systems can listen to this event
    // - Send confirmation email
    // - Update inventory
    // - Notify warehouse
    // - Track analytics

    return $this->state->withOrder($order);
}
```

### Polling for Real-Time Updates

```php
use App\Framework\LiveComponents\Contracts\Pollable;

#[LiveComponent('order-status')]
final readonly class OrderStatusComponent implements
    LiveComponentContract,
    Pollable
{
    #[Action]
    public function poll(): OrderStatusState
    {
        // Fetch latest order status
        $order = $this->orderRepository->find($this->state->orderId);

        return $this->state->withStatus($order->status);
    }

    public function getPollInterval(): int
    {
        return 5000; // Poll every 5 seconds
    }
}
```

### Fragment-Based Updates

```php
// Template with fragments
<div data-lc-component="product-list">
    <!-- Only update this part -->
    <div data-lc-fragment="product-grid">
        <for items="products" as="product">
            <!-- Product cards -->
        </for>
    </div>

    <!-- Only update this part -->
    <div data-lc-fragment="pagination">
        <!-- Pagination controls -->
    </div>
</div>

// JavaScript
liveComponent.executeAction('changePage', { page: 2 }, {
    fragments: ['product-grid', 'pagination'] // Only update these
});
```

### DTOs for Complex Parameters

```php
// Define DTO
final readonly class UpdateProfileRequest
{
    public function __construct(
        public string $name,
        public Email $email,
        public ?string $bio = null,
        public bool $newsletter = false
    ) {}
}

// Use in action
#[Action]
public function updateProfile(UpdateProfileRequest $request): UserProfileState
{
    // $request is automatically instantiated from action parameters
    return $this->state->update(
        name: $request->name,
        email: $request->email,
        bio: $request->bio,
        newsletter: $request->newsletter
    );
}
```

---

## Testing

See `tests/Feature/LiveComponents/CounterComponentTest.php` for examples.

### Testing Helpers

```php
use function Pest\LiveComponents\mountComponent;
use function Pest\LiveComponents\callAction;
use function Pest\LiveComponents\callActionWithFragments;

// Mount component
$component = mountComponent('counter:test', ['count' => 0]);

// Execute action
$result = callAction($component, 'increment');

// Execute action with fragments
$result = callActionWithFragments(
    $component,
    'updateGrid',
    params: ['page' => 2],
    fragments: ['product-grid', 'pagination']
);
```

### Custom Expectations

```php
// HTML assertions
expect($component['html'])->toContainHtml('Count: 5');

// State assertions
expect($component['state'])->toHaveState(['count' => 5]);
expect($component['state'])->toHaveStateKey('count', 5);

// Event assertions
expect($result['events'])->toHaveDispatchedEvent('counter:changed');
expect($result['events'])->toHaveDispatchedEventWithData('counter:changed', [
    'old_value' => 4,
    'new_value' => 5
]);

// Fragment assertions
expect($result['html'])->toHaveFragment('cart-summary', '€99.99');
```

---

## Deployment

### Production Checklist

- [ ] All actions have `#[Action]` attribute
- [ ] Rate limits configured for critical actions
- [ ] Idempotency TTL set for state-changing actions
- [ ] CSRF protection enabled (default)
- [ ] Events logged for analytics
- [ ] Error handling implemented
- [ ] Tests passing
- [ ] Templates optimized (fragments for large updates)
- [ ] JavaScript client configured
- [ ] Monitoring/alerting set up

### Performance Tips

1. **Use fragments** for partial updates
2. **Configure polling interval** based on data freshness needs
3. **Add caching** via `#[Action(cache: true)]`
4. **Optimize templates** - minimize DOM size
5. **Lazy load** components not immediately visible

### Security Tips

1. **Never trust client input** - validate in actions
2. **Use DTOs** for complex parameters
3. **Set rate limits** on all actions
4. **Enable idempotency** for critical operations
5. **Log security events** for monitoring

---

## Troubleshooting

### Component Not Found

**Error**: "Component 'my-component' not found"

**Causes**:
- Missing `#[LiveComponent]` attribute
- Typo in component name
- Component class not in `Application/LiveComponents/` directory
- Discovery cache stale

**Solutions**:
```bash
# Clear discovery cache
php console.php discovery:clear

# Verify component is registered
php console.php mcp:server
# Then use analyze_codebase to find LiveComponents
```

### Action Not Callable

**Error**: "Action 'myAction' not found"

**Causes**:
- Missing `#[Action]` attribute
- Method is private/protected
- Method is static
- Reserved method name (render, toArray, etc.)

**Solutions**:
```php
// ✅ Correct
#[Action]
public function myAction(): StateType

// ❌ Wrong - missing attribute
public function myAction(): StateType

// ❌ Wrong - static
#[Action]
public static function myAction(): StateType

// ❌ Wrong - private
#[Action]
private function myAction(): StateType
```

### State Not Updating

**Causes**:
- Action not returning new state
- State mutation instead of new instance
- Caching issues

**Solutions**:
```php
// ❌ Wrong - mutation (doesn't work with readonly)
public function increment(): void
{
    $this->state->count++; // Can't mutate readonly
}

// ✅ Correct - return new state
public function increment(): CounterState
{
    return $this->state->increment(); // Returns new instance
}
```

### Events Not Dispatching

**Causes**:
- Missing `ComponentEventDispatcher` parameter
- Not calling `dispatch()` method
- Events not handled in JavaScript

**Solutions**:
```php
// ✅ Correct
#[Action]
public function save(?ComponentEventDispatcher $events = null): StateType
{
    $events?->dispatch('saved', EventPayload::fromArray(['id' => 123]));
    return $newState;
}

// JavaScript
liveComponent.on('saved', (data) => {
    console.log('Saved:', data.id);
});
```

---

## Next Steps

- Read [Security Guide](SECURITY-GUIDE.md) for CSRF/Rate Limiting/Idempotency
- Read [Performance Guide](PERFORMANCE-GUIDE.md) for optimization strategies
- Read [API Reference](API-REFERENCE.md) for complete attribute/contract documentation
- Explore example components in `src/Application/LiveComponents/`
- Run tests: `./vendor/bin/pest tests/Feature/LiveComponents/`

---

## Summary

LiveComponents provide:

✅ **Server-rendered interactivity** without JavaScript complexity
✅ **Type-safe state management** with Value Objects
✅ **Built-in security** (CSRF, Rate Limiting, Idempotency)
✅ **Framework-compliant** (Readonly, Immutable, Composition)
✅ **Easy testing** with Pest helpers
✅ **Production-ready** with monitoring and error handling

**Start simple** with a counter, then build more complex components as you learn the patterns.
