- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
979 lines
20 KiB
Markdown
979 lines
20 KiB
Markdown
# LiveComponents Troubleshooting Guide
|
|
|
|
**Common Issues and Solutions**
|
|
|
|
This guide helps diagnose and resolve common LiveComponents problems.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Component Not Responding](#component-not-responding)
|
|
2. [State Synchronization Issues](#state-synchronization-issues)
|
|
3. [CSRF Token Errors](#csrf-token-errors)
|
|
4. [Rate Limiting Issues](#rate-limiting-issues)
|
|
5. [SSE Connection Problems](#sse-connection-problems)
|
|
6. [Performance Issues](#performance-issues)
|
|
7. [File Upload Problems](#file-upload-problems)
|
|
8. [Fragment Rendering Issues](#fragment-rendering-issues)
|
|
9. [Browser Compatibility](#browser-compatibility)
|
|
10. [Debugging Tools](#debugging-tools)
|
|
|
|
---
|
|
|
|
## Component Not Responding
|
|
|
|
### Symptoms
|
|
- Clicking buttons does nothing
|
|
- Forms don't submit
|
|
- No network requests in DevTools
|
|
- Component appears static
|
|
|
|
### Diagnostic Steps
|
|
|
|
**1. Check Component Initialization**
|
|
```javascript
|
|
// Open browser console
|
|
console.log(LiveComponent.components);
|
|
|
|
// Should show active components
|
|
// If empty, component not initialized
|
|
```
|
|
|
|
**2. Verify Component Markup**
|
|
```html
|
|
<!-- Required attributes -->
|
|
<div data-component-id="{component_id}" data-component-name="ComponentName">
|
|
<!-- Content -->
|
|
</div>
|
|
```
|
|
|
|
**3. Check JavaScript Loading**
|
|
```javascript
|
|
// In browser console
|
|
typeof LiveComponent !== 'undefined' // Should be true
|
|
```
|
|
|
|
**4. Inspect Browser Console**
|
|
```
|
|
F12 → Console Tab
|
|
Look for JavaScript errors
|
|
```
|
|
|
|
### Common Causes & Solutions
|
|
|
|
#### Missing `data-component-id`
|
|
```html
|
|
<!-- ❌ Wrong - missing required attribute -->
|
|
<div class="component">
|
|
<button data-lc-action="save">Save</button>
|
|
</div>
|
|
|
|
<!-- ✅ Correct -->
|
|
<div data-component-id="{component_id}" data-component-name="MyComponent">
|
|
<button data-lc-action="save">Save</button>
|
|
</div>
|
|
```
|
|
|
|
#### JavaScript Not Loaded
|
|
```bash
|
|
# Rebuild JavaScript assets
|
|
npm run build
|
|
|
|
# Check if file exists
|
|
ls public/assets/js/livecomponent.js
|
|
|
|
# Development mode
|
|
npm run dev
|
|
```
|
|
|
|
#### HTTPS Not Enabled
|
|
```bash
|
|
# LiveComponents requires HTTPS
|
|
# Start with HTTPS
|
|
make up
|
|
|
|
# Access via
|
|
https://localhost # ✅ Correct
|
|
http://localhost # ❌ Wrong - will fail
|
|
```
|
|
|
|
#### Component Class Not Found
|
|
```php
|
|
// Verify class exists and namespace is correct
|
|
namespace App\Application\LiveComponents;
|
|
|
|
final class MyComponent extends LiveComponent
|
|
{
|
|
// Implementation
|
|
}
|
|
|
|
// In controller
|
|
LiveComponent::mount(MyComponent::class) // ✅
|
|
LiveComponent::mount('MyComponent') // ❌ Wrong
|
|
```
|
|
|
|
---
|
|
|
|
## State Synchronization Issues
|
|
|
|
### Symptoms
|
|
- Property changes don't reflect in UI
|
|
- UI updates but server state wrong
|
|
- State resets unexpectedly
|
|
|
|
### Diagnostic Steps
|
|
|
|
**1. Check LiveProp Attribute**
|
|
```php
|
|
// ❌ Wrong - missing attribute
|
|
public string $name = '';
|
|
|
|
// ✅ Correct
|
|
#[LiveProp]
|
|
public string $name = '';
|
|
```
|
|
|
|
**2. Verify Property Type Compatibility**
|
|
```php
|
|
// ✅ Supported types
|
|
#[LiveProp] public string $text;
|
|
#[LiveProp] public int $count;
|
|
#[LiveProp] public float $price;
|
|
#[LiveProp] public bool $active;
|
|
#[LiveProp] public array $items;
|
|
|
|
// ❌ Not supported - can't serialize
|
|
#[LiveProp] public \Closure $callback;
|
|
#[LiveProp] public Resource $handle;
|
|
#[LiveProp] public PDO $connection;
|
|
```
|
|
|
|
**3. Inspect Current State**
|
|
```javascript
|
|
// Get component state
|
|
const component = LiveComponent.getComponent('component-id');
|
|
console.log('Current state:', component.state);
|
|
```
|
|
|
|
### Common Causes & Solutions
|
|
|
|
#### Property Not Marked as LiveProp
|
|
```php
|
|
// Problem: Property changes not synced
|
|
public string $searchTerm = ''; // ❌ Missing #[LiveProp]
|
|
|
|
#[LiveAction]
|
|
public function search(): void
|
|
{
|
|
// $this->searchTerm not synced with client
|
|
$this->results = $this->searchService->search($this->searchTerm);
|
|
}
|
|
|
|
// Solution: Add attribute
|
|
#[LiveProp]
|
|
public string $searchTerm = ''; // ✅ Now syncs
|
|
```
|
|
|
|
#### Complex Object Serialization
|
|
```php
|
|
// Problem: Object can't be serialized
|
|
#[LiveProp]
|
|
public User $user; // ❌ Complex object
|
|
|
|
// Solution: Use Value Object or primitives
|
|
#[LiveProp]
|
|
public string $userId;
|
|
|
|
private User $user; // Not serialized
|
|
|
|
public function hydrate(array $state): void
|
|
{
|
|
parent::hydrate($state);
|
|
|
|
// Reload user from ID
|
|
$this->user = $this->userRepository->find($this->userId);
|
|
}
|
|
```
|
|
|
|
#### State Checksum Mismatch
|
|
```javascript
|
|
// Monitor checksum errors
|
|
window.addEventListener('livecomponent:error', (e) => {
|
|
if (e.detail.code === 'CHECKSUM_MISMATCH') {
|
|
console.error('State corrupted - refreshing component');
|
|
LiveComponent.refresh(e.detail.componentId);
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## CSRF Token Errors
|
|
|
|
### Symptoms
|
|
- "CSRF token mismatch" error
|
|
- 403 Forbidden responses
|
|
- Actions fail silently
|
|
|
|
### Diagnostic Steps
|
|
|
|
**1. Check CSRF Meta Tag**
|
|
```html
|
|
<!-- Must be in <head> -->
|
|
<meta name="csrf-token" content="{csrf_token}">
|
|
```
|
|
|
|
**2. Verify Token in Requests**
|
|
```
|
|
F12 → Network Tab → Select request → Headers
|
|
Should have: X-CSRF-Token: {token}
|
|
```
|
|
|
|
**3. Check Token Expiry**
|
|
```php
|
|
// .env configuration
|
|
CSRF_TOKEN_LIFETIME=7200 // 2 hours (default)
|
|
```
|
|
|
|
### Common Causes & Solutions
|
|
|
|
#### Missing CSRF Meta Tag
|
|
```html
|
|
<!-- ❌ Wrong - no CSRF token -->
|
|
<head>
|
|
<title>My App</title>
|
|
</head>
|
|
|
|
<!-- ✅ Correct - token included -->
|
|
<head>
|
|
<title>My App</title>
|
|
<meta name="csrf-token" content="{csrf_token}">
|
|
</head>
|
|
```
|
|
|
|
#### Token Expired
|
|
```javascript
|
|
// Automatic token refresh
|
|
window.addEventListener('livecomponent:error', (e) => {
|
|
if (e.detail.code === 'CSRF_TOKEN_EXPIRED') {
|
|
// Refresh token
|
|
LiveComponent.refreshCsrfToken().then(() => {
|
|
// Retry failed action
|
|
LiveComponent.retryLastAction();
|
|
});
|
|
}
|
|
});
|
|
```
|
|
|
|
#### Session Cleared
|
|
```php
|
|
// Problem: Session expired/cleared
|
|
// Solution: Redirect to login
|
|
window.addEventListener('livecomponent:error', (e) => {
|
|
if (e.detail.code === 'SESSION_EXPIRED') {
|
|
window.location.href = '/login?redirect=' + window.location.pathname;
|
|
}
|
|
});
|
|
```
|
|
|
|
#### Development vs Production
|
|
```env
|
|
# Development - lenient
|
|
CSRF_TOKEN_LIFETIME=86400 # 24 hours
|
|
|
|
# Production - strict
|
|
CSRF_TOKEN_LIFETIME=3600 # 1 hour
|
|
CSRF_REGENERATE_ON_ACTION=true
|
|
```
|
|
|
|
---
|
|
|
|
## Rate Limiting Issues
|
|
|
|
### Symptoms
|
|
- "Too many requests" error (429)
|
|
- Actions blocked after rapid clicks
|
|
- "Retry-After" headers in response
|
|
|
|
### Diagnostic Steps
|
|
|
|
**1. Check Rate Limit Configuration**
|
|
```env
|
|
LIVECOMPONENT_RATE_LIMIT=60 # Default: 60/minute
|
|
LIVECOMPONENT_RATE_LIMIT_WINDOW=60 # Window in seconds
|
|
```
|
|
|
|
**2. Monitor Rate Limit Events**
|
|
```javascript
|
|
window.addEventListener('livecomponent:rate-limited', (e) => {
|
|
console.warn('Rate limited:', {
|
|
action: e.detail.action,
|
|
retryAfter: e.detail.retryAfter
|
|
});
|
|
});
|
|
```
|
|
|
|
**3. Check Component-Specific Limits**
|
|
```php
|
|
#[RateLimit(requests: 10, window: 60)]
|
|
final class SearchComponent extends LiveComponent
|
|
{
|
|
// More restrictive than global limit
|
|
}
|
|
```
|
|
|
|
### Common Causes & Solutions
|
|
|
|
#### Too Aggressive Rate Limit
|
|
```env
|
|
# Problem: Rate limit too low
|
|
LIVECOMPONENT_RATE_LIMIT=10 # Too restrictive
|
|
|
|
# Solution: Increase for normal usage
|
|
LIVECOMPONENT_RATE_LIMIT=60 # More reasonable
|
|
```
|
|
|
|
#### Rapid User Actions
|
|
```php
|
|
// Problem: User clicking too fast
|
|
#[LiveAction]
|
|
public function search(): void
|
|
{
|
|
// Triggered on every keystroke
|
|
}
|
|
|
|
// Solution: Use debouncing
|
|
```
|
|
|
|
```html
|
|
<input
|
|
type="text"
|
|
data-lc-model.debounce.500="searchTerm"
|
|
value="{searchTerm}"
|
|
/>
|
|
```
|
|
|
|
#### Bot/Scraper Activity
|
|
```php
|
|
use App\Framework\Http\Middlewares\RateLimitMiddleware;
|
|
|
|
// Implement stricter limits for suspicious patterns
|
|
#[RateLimit(requests: 5, window: 300)] // 5 per 5 minutes
|
|
final class PublicApiComponent extends LiveComponent
|
|
{
|
|
// Public-facing, needs protection
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## SSE Connection Problems
|
|
|
|
### Symptoms
|
|
- Real-time updates not working
|
|
- Connection drops frequently
|
|
- "SSE disconnected" in console
|
|
|
|
### Diagnostic Steps
|
|
|
|
**1. Verify HTTPS**
|
|
```
|
|
SSE requires HTTPS in production
|
|
https://localhost ✅
|
|
http://localhost ❌
|
|
```
|
|
|
|
**2. Check SSE Configuration**
|
|
```env
|
|
LIVECOMPONENT_SSE_ENABLED=true
|
|
LIVECOMPONENT_SSE_HEARTBEAT=15 # Heartbeat interval
|
|
```
|
|
|
|
**3. Monitor SSE Connection**
|
|
```javascript
|
|
window.addEventListener('livecomponent:sse-connected', () => {
|
|
console.log('SSE connected');
|
|
});
|
|
|
|
window.addEventListener('livecomponent:sse-disconnected', (e) => {
|
|
console.error('SSE disconnected:', e.detail.reason);
|
|
});
|
|
```
|
|
|
|
**4. Check Server Logs**
|
|
```bash
|
|
docker logs php | grep SSE
|
|
```
|
|
|
|
### Common Causes & Solutions
|
|
|
|
#### HTTPS Not Enabled
|
|
```bash
|
|
# Problem: HTTP used in production
|
|
# Solution: Enable HTTPS
|
|
|
|
# Development
|
|
make up # Automatically uses HTTPS
|
|
|
|
# Production
|
|
# Configure SSL certificates in nginx/apache
|
|
```
|
|
|
|
#### Firewall Blocking SSE
|
|
```nginx
|
|
# nginx configuration
|
|
location /livecomponent/sse {
|
|
proxy_pass http://php-fpm;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Connection "";
|
|
proxy_buffering off;
|
|
proxy_cache off;
|
|
proxy_read_timeout 86400s; # 24 hours
|
|
}
|
|
```
|
|
|
|
#### Connection Timeout
|
|
```php
|
|
// Increase SSE timeout
|
|
set_time_limit(0); // No timeout
|
|
ignore_user_abort(true);
|
|
```
|
|
|
|
#### Auto-Reconnection Not Working
|
|
```javascript
|
|
// Manual reconnection logic
|
|
let reconnectAttempts = 0;
|
|
const maxReconnectAttempts = 5;
|
|
|
|
window.addEventListener('livecomponent:sse-disconnected', (e) => {
|
|
if (reconnectAttempts < maxReconnectAttempts) {
|
|
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
|
|
|
|
setTimeout(() => {
|
|
console.log(`Reconnecting SSE (attempt ${reconnectAttempts + 1})`);
|
|
LiveComponent.reconnectSse(e.detail.componentId);
|
|
reconnectAttempts++;
|
|
}, delay);
|
|
}
|
|
});
|
|
|
|
window.addEventListener('livecomponent:sse-connected', () => {
|
|
reconnectAttempts = 0; // Reset on successful connection
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Issues
|
|
|
|
### Symptoms
|
|
- Slow action responses (>500ms)
|
|
- UI freezes during updates
|
|
- High memory usage
|
|
- Browser lag
|
|
|
|
### Diagnostic Steps
|
|
|
|
**1. Enable Performance Profiling**
|
|
```javascript
|
|
localStorage.setItem('livecomponent_devtools', 'true');
|
|
localStorage.setItem('livecomponent_profiling', 'true');
|
|
location.reload();
|
|
```
|
|
|
|
**2. Check Action Latency**
|
|
```javascript
|
|
window.addEventListener('livecomponent:action-executed', (e) => {
|
|
if (e.detail.duration > 200) {
|
|
console.warn('Slow action:', {
|
|
action: e.detail.action,
|
|
duration: e.detail.duration
|
|
});
|
|
}
|
|
});
|
|
```
|
|
|
|
**3. Monitor Memory Usage**
|
|
```
|
|
F12 → Performance Tab → Memory
|
|
Record session and analyze heap snapshots
|
|
```
|
|
|
|
**4. Analyze Network Requests**
|
|
```
|
|
F12 → Network Tab
|
|
Check:
|
|
- Request count (should be low with batching)
|
|
- Payload sizes
|
|
- Response times
|
|
```
|
|
|
|
### Common Causes & Solutions
|
|
|
|
#### Large Component State
|
|
```php
|
|
// Problem: Serializing too much data
|
|
#[LiveProp]
|
|
public array $allProducts = []; // ❌ 10,000 products
|
|
|
|
// Solution: Pagination
|
|
#[LiveProp]
|
|
public int $page = 1;
|
|
|
|
#[LiveProp]
|
|
public int $perPage = 50;
|
|
|
|
private array $products = []; // ✅ Not serialized
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->loadPage();
|
|
}
|
|
|
|
private function loadPage(): void
|
|
{
|
|
$this->products = $this->productRepository->paginate(
|
|
page: $this->page,
|
|
perPage: $this->perPage
|
|
);
|
|
}
|
|
```
|
|
|
|
#### No Fragment Rendering
|
|
```php
|
|
// Problem: Re-rendering entire component
|
|
#[LiveAction]
|
|
public function updateStats(): void
|
|
{
|
|
$this->stats = $this->calculateStats();
|
|
// Full component re-render ❌
|
|
}
|
|
|
|
// Solution: Use fragments
|
|
#[LiveAction]
|
|
#[Fragment('stats-section')]
|
|
public function updateStats(): void
|
|
{
|
|
$this->stats = $this->calculateStats();
|
|
// Only stats fragment re-renders ✅
|
|
}
|
|
```
|
|
|
|
#### N+1 Query Problem
|
|
```php
|
|
// Problem: N+1 queries
|
|
public function render(): string
|
|
{
|
|
foreach ($this->orders as $order) {
|
|
$customer = $this->customerRepository->find($order->customerId);
|
|
// ❌ Query per order
|
|
}
|
|
}
|
|
|
|
// Solution: Eager loading
|
|
public function mount(): void
|
|
{
|
|
$this->orders = $this->orderRepository->findWithCustomers();
|
|
// ✅ Single query with JOIN
|
|
}
|
|
```
|
|
|
|
#### Too Many Components
|
|
```
|
|
Problem: >100 components on page
|
|
Solution:
|
|
1. Lazy load components
|
|
2. Use virtual scrolling
|
|
3. Implement pagination
|
|
```
|
|
|
|
See [Performance Guide](performance-guide.md) for comprehensive optimization strategies.
|
|
|
|
---
|
|
|
|
## File Upload Problems
|
|
|
|
### Symptoms
|
|
- Upload fails silently
|
|
- Progress stuck at 0%
|
|
- Chunk upload errors
|
|
- File size exceeded errors
|
|
|
|
### Diagnostic Steps
|
|
|
|
**1. Check Upload Configuration**
|
|
```html
|
|
<input
|
|
type="file"
|
|
data-lc-upload="handleUpload"
|
|
data-chunk-size="1048576"
|
|
data-max-file-size="104857600"
|
|
/>
|
|
```
|
|
|
|
**2. Monitor Upload Events**
|
|
```javascript
|
|
window.addEventListener('livecomponent:upload-progress', (e) => {
|
|
console.log(`Upload: ${e.detail.progress}%`);
|
|
});
|
|
|
|
window.addEventListener('livecomponent:upload-error', (e) => {
|
|
console.error('Upload failed:', e.detail.error);
|
|
});
|
|
```
|
|
|
|
**3. Check Server Limits**
|
|
```php
|
|
// php.ini
|
|
upload_max_filesize = 100M
|
|
post_max_size = 100M
|
|
max_execution_time = 300
|
|
```
|
|
|
|
### Common Causes & Solutions
|
|
|
|
#### File Size Exceeds Limit
|
|
```html
|
|
<!-- Problem: No validation -->
|
|
<input type="file" data-lc-upload="handleUpload" />
|
|
|
|
<!-- Solution: Set max size -->
|
|
<input
|
|
type="file"
|
|
data-lc-upload="handleUpload"
|
|
data-max-file-size="104857600"
|
|
accept=".pdf,.jpg,.png"
|
|
/>
|
|
```
|
|
|
|
```javascript
|
|
// Client-side validation
|
|
window.addEventListener('livecomponent:upload-error', (e) => {
|
|
if (e.detail.code === 'FILE_TOO_LARGE') {
|
|
alert(`File too large. Max size: ${e.detail.maxSize / 1024 / 1024}MB`);
|
|
}
|
|
});
|
|
```
|
|
|
|
#### Chunk Upload Failure
|
|
```php
|
|
// Server-side: Implement retry logic
|
|
final class ChunkUploadHandler
|
|
{
|
|
public function storeChunk(
|
|
string $uploadId,
|
|
int $chunkIndex,
|
|
string $data
|
|
): void {
|
|
$attempts = 0;
|
|
$maxAttempts = 3;
|
|
|
|
while ($attempts < $maxAttempts) {
|
|
try {
|
|
$this->storage->put(
|
|
"uploads/{$uploadId}/chunk_{$chunkIndex}",
|
|
$data
|
|
);
|
|
return;
|
|
} catch (\Exception $e) {
|
|
$attempts++;
|
|
if ($attempts >= $maxAttempts) {
|
|
throw $e;
|
|
}
|
|
usleep(100000 * $attempts); // Exponential backoff
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Upload State Lost
|
|
```php
|
|
// Problem: Upload state not persisted
|
|
// Solution: Store upload state
|
|
final class UploadStateRepository
|
|
{
|
|
public function saveState(UploadState $state): void
|
|
{
|
|
$this->cache->set(
|
|
"upload_state:{$state->uploadId}",
|
|
serialize($state),
|
|
Duration::fromHours(24)
|
|
);
|
|
}
|
|
|
|
public function restoreState(string $uploadId): ?UploadState
|
|
{
|
|
$data = $this->cache->get("upload_state:{$uploadId}");
|
|
return $data ? unserialize($data) : null;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Fragment Rendering Issues
|
|
|
|
### Symptoms
|
|
- Fragment not updating
|
|
- Entire component re-renders
|
|
- Focus lost after update
|
|
- Nested fragments broken
|
|
|
|
### Diagnostic Steps
|
|
|
|
**1. Verify Fragment Markup**
|
|
```html
|
|
<!-- Check fragment attribute -->
|
|
<div data-lc-fragment="section-name">
|
|
<!-- Content -->
|
|
</div>
|
|
```
|
|
|
|
**2. Check Action Attribute**
|
|
```php
|
|
#[LiveAction]
|
|
#[Fragment('section-name')] // Must match HTML
|
|
public function updateSection(): void
|
|
{
|
|
// Update logic
|
|
}
|
|
```
|
|
|
|
**3. Monitor Fragment Updates**
|
|
```javascript
|
|
window.addEventListener('livecomponent:fragment-updated', (e) => {
|
|
console.log('Fragment updated:', {
|
|
name: e.detail.fragmentName,
|
|
duration: e.detail.duration
|
|
});
|
|
});
|
|
```
|
|
|
|
### Common Causes & Solutions
|
|
|
|
#### Fragment Name Mismatch
|
|
```php
|
|
// Problem: Names don't match
|
|
#[Fragment('user-stats')] // ❌ kebab-case in PHP
|
|
public function updateStats(): void { }
|
|
```
|
|
|
|
```html
|
|
<!-- userStats in HTML ❌ -->
|
|
<div data-lc-fragment="userStats">
|
|
```
|
|
|
|
```php
|
|
// Solution: Use consistent naming
|
|
#[Fragment('user-stats')] // ✅ kebab-case
|
|
```
|
|
|
|
```html
|
|
<div data-lc-fragment="user-stats"> <!-- ✅ Matches -->
|
|
```
|
|
|
|
#### Missing Fragment Attribute
|
|
```php
|
|
// Problem: Fragment specified but no HTML marker
|
|
#[LiveAction]
|
|
#[Fragment('results')]
|
|
public function search(): void { }
|
|
|
|
// Template missing fragment marker
|
|
<div>
|
|
<!-- ❌ No data-lc-fragment="results" -->
|
|
<for items="results" as="result">
|
|
<div>{result.title}</div>
|
|
</for>
|
|
</div>
|
|
|
|
// Solution: Add fragment marker
|
|
<div data-lc-fragment="results"> <!-- ✅ Added -->
|
|
<for items="results" as="result">
|
|
<div>{result.title}</div>
|
|
</for>
|
|
</div>
|
|
```
|
|
|
|
#### Focus Lost After Update
|
|
```javascript
|
|
// Problem: Input focus lost during fragment update
|
|
// Solution: Framework preserves focus automatically
|
|
// If not working, check:
|
|
|
|
// 1. Is input inside fragment?
|
|
<div data-lc-fragment="search">
|
|
<input type="text" id="search-input" /> ✅
|
|
</div>
|
|
|
|
// 2. Does input have stable ID?
|
|
<input type="text" id="search-input" /> ✅ Has ID
|
|
<input type="text" /> ❌ No ID
|
|
```
|
|
|
|
---
|
|
|
|
## Browser Compatibility
|
|
|
|
### Symptoms
|
|
- Works in Chrome but not Safari
|
|
- Mobile browser issues
|
|
- Older browser errors
|
|
|
|
### Supported Browsers
|
|
- Chrome/Edge: 90+
|
|
- Firefox: 88+
|
|
- Safari: 14+
|
|
- Mobile Safari: 14+
|
|
- Mobile Chrome: 90+
|
|
|
|
### Common Issues
|
|
|
|
#### ES2020 Features Not Supported
|
|
```javascript
|
|
// Problem: Older browser lacks ES2020
|
|
// Solution: Build with polyfills
|
|
|
|
// vite.config.js
|
|
export default defineConfig({
|
|
build: {
|
|
target: 'es2015', // Broader compatibility
|
|
polyfillModulePreload: true
|
|
}
|
|
});
|
|
```
|
|
|
|
#### EventSource Not Supported
|
|
```javascript
|
|
// Check SSE support
|
|
if (typeof EventSource === 'undefined') {
|
|
console.warn('SSE not supported - real-time updates disabled');
|
|
// Fallback to polling
|
|
}
|
|
```
|
|
|
|
#### iOS Safari Issues
|
|
```javascript
|
|
// iOS Safari has stricter CORS requirements
|
|
// Ensure proper CORS headers
|
|
|
|
// PHP
|
|
header('Access-Control-Allow-Origin: https://yourdomain.com');
|
|
header('Access-Control-Allow-Credentials: true');
|
|
```
|
|
|
|
---
|
|
|
|
## Debugging Tools
|
|
|
|
### DevTools Panel
|
|
|
|
**Enable DevTools**:
|
|
```env
|
|
LIVECOMPONENT_DEVTOOLS_ENABLED=true
|
|
```
|
|
|
|
Or via localStorage:
|
|
```javascript
|
|
localStorage.setItem('livecomponent_devtools', 'true');
|
|
location.reload();
|
|
```
|
|
|
|
**Features**:
|
|
- Component tree inspection
|
|
- Action log with timing
|
|
- Event log
|
|
- Performance profiling
|
|
- Network monitoring
|
|
- DOM badges
|
|
|
|
### Console Commands
|
|
|
|
```javascript
|
|
// Get all components
|
|
LiveComponent.components
|
|
|
|
// Get specific component
|
|
const component = LiveComponent.getComponent('component-id');
|
|
|
|
// Inspect state
|
|
console.log(component.state);
|
|
|
|
// Execute action manually
|
|
LiveComponent.executeAction('component-id', 'actionName', {
|
|
param: 'value'
|
|
});
|
|
|
|
// Refresh component
|
|
LiveComponent.refresh('component-id');
|
|
|
|
// Get performance metrics
|
|
LiveComponent.getMetrics('component-id');
|
|
```
|
|
|
|
### Server-Side Debugging
|
|
|
|
```php
|
|
// Log component state
|
|
$this->logger->debug('Component state', [
|
|
'component' => static::class,
|
|
'state' => $this->getState()
|
|
]);
|
|
|
|
// Performance tracking
|
|
$start = hrtime(true);
|
|
$result = $this->expensiveOperation();
|
|
$duration = (hrtime(true) - $start) / 1e6;
|
|
|
|
$this->logger->info('Operation completed', [
|
|
'duration_ms' => $duration
|
|
]);
|
|
```
|
|
|
|
### Network Debugging
|
|
|
|
```
|
|
F12 → Network Tab → Filter: livecomponent
|
|
|
|
Check:
|
|
- Request payloads
|
|
- Response times
|
|
- Error responses
|
|
- Batching efficiency
|
|
```
|
|
|
|
---
|
|
|
|
## Getting Help
|
|
|
|
### Before Asking for Help
|
|
|
|
1. **Check this guide** for common solutions
|
|
2. **Enable DevTools** and check console
|
|
3. **Reproduce in isolation** (minimal example)
|
|
4. **Gather error messages** (browser console + server logs)
|
|
|
|
### Reporting Issues
|
|
|
|
Include:
|
|
- Browser + version
|
|
- PHP version
|
|
- Framework version
|
|
- Component code (minimal reproduction)
|
|
- Error messages
|
|
- Steps to reproduce
|
|
|
|
### Resources
|
|
|
|
- **Documentation**: [Getting Started](01-getting-started.md), [API Reference](api-reference.md)
|
|
- **Examples**: `src/Application/LiveComponents/`
|
|
- **Tests**: `tests/e2e/livecomponents-*.spec.js`
|
|
- **Security**: security@example.com
|
|
- **Bugs**: GitHub Issues
|
|
|
|
---
|
|
|
|
**Next**: [FAQ](faq.md) →
|