# Libra - Coding Standards

> Extracted from `docs/architecture.md` Section 20

## Critical Rules

| Rule | Description |
|------|-------------|
| **Volt Pattern** | Use class-based Volt components (per CLAUDE.md) |
| **Flux UI** | Use Flux components when available |
| **Form Validation** | Always use Form Request or Livewire form objects |
| **Eloquent** | Prefer `Model::query()` over `DB::` facade |
| **Actions** | Use single-purpose Action classes for business logic |
| **Testing** | Every feature must have corresponding Pest tests |
| **Pint** | Run `vendor/bin/pint --dirty` before committing |

## Naming Conventions

| Element | Convention | Example |
|---------|------------|---------|
| Models | Singular PascalCase | `Consultation` |
| Tables | Plural snake_case | `consultations` |
| Columns | snake_case | `booking_date` |
| Controllers | PascalCase + Controller | `ConsultationController` |
| Actions | Verb + Noun + Action | `CreateConsultationAction` |
| Jobs | Verb + Noun | `SendBookingNotification` |
| Events | Past tense | `ConsultationApproved` |
| Listeners | Verb phrase | `SendApprovalNotification` |
| Volt Components | kebab-case path | `admin/consultations/index` |
| Enums | PascalCase | `ConsultationStatus` |
| Enum Cases | PascalCase | `NoShow` |
| Traits | Adjective or -able | `HasConsultations`, `Bookable` |
| Interfaces | Adjective or -able | `Exportable` |

## Volt Component Template

```php
<?php
// resources/views/livewire/admin/consultations/index.blade.php

use App\Models\Consultation;
use App\Enums\ConsultationStatus;
use Livewire\Volt\Component;
use Livewire\WithPagination;

new class extends Component {
    use WithPagination;

    public string $search = '';
    public string $status = '';

    public function updatedSearch(): void
    {
        $this->resetPage();
    }

    public function updatedStatus(): void
    {
        $this->resetPage();
    }

    public function with(): array
    {
        return [
            'consultations' => Consultation::query()
                ->with('user:id,full_name,email')
                ->when($this->search, fn ($q) => $q->whereHas('user', fn ($q) =>
                    $q->where('full_name', 'like', "%{$this->search}%")
                ))
                ->when($this->status, fn ($q) => $q->where('status', $this->status))
                ->orderBy('booking_date', 'desc')
                ->paginate(15),
            'statuses' => ConsultationStatus::cases(),
        ];
    }
}; ?>

<div>
    <x-slot name="header">
        <flux:heading size="xl">{{ __('models.consultations') }}</flux:heading>
    </x-slot>

    <div class="flex gap-4 mb-6">
        <flux:input
            wire:model.live.debounce.300ms="search"
            placeholder="{{ __('messages.search') }}"
            icon="magnifying-glass"
        />

        <flux:select wire:model.live="status">
            <option value="">{{ __('messages.all_statuses') }}</option>
            @foreach($statuses as $s)
                <option value="{{ $s->value }}">{{ $s->label() }}</option>
            @endforeach
        </flux:select>
    </div>

    <div class="space-y-4">
        @forelse($consultations as $consultation)
            <x-ui.card wire:key="consultation-{{ $consultation->id }}">
                {{-- Card content --}}
            </x-ui.card>
        @empty
            <x-ui.empty-state :message="__('messages.no_consultations')" />
        @endforelse
    </div>

    <div class="mt-6">
        {{ $consultations->links() }}
    </div>
</div>
```

## Action Class Template

```php
<?php
// app/Actions/Consultation/CreateConsultationAction.php

namespace App\Actions\Consultation;

use App\Models\Consultation;
use App\Models\User;
use App\Enums\ConsultationStatus;
use App\Enums\PaymentStatus;
use App\Jobs\SendBookingNotification;
use App\Exceptions\BookingLimitExceededException;
use Illuminate\Support\Facades\DB;

class CreateConsultationAction
{
    public function execute(User $user, array $data): Consultation
    {
        // Validate booking limit
        if ($user->hasBookingOnDate($data['booking_date'])) {
            throw new BookingLimitExceededException(
                __('messages.booking_limit_exceeded')
            );
        }

        return DB::transaction(function () use ($user, $data) {
            $consultation = Consultation::create([
                'user_id' => $user->id,
                'booking_date' => $data['booking_date'],
                'booking_time' => $data['booking_time'],
                'problem_summary' => $data['problem_summary'],
                'status' => ConsultationStatus::Pending,
                'payment_status' => PaymentStatus::NotApplicable,
            ]);

            SendBookingNotification::dispatch($consultation);

            return $consultation;
        });
    }
}
```

## Testing Standards

- Use **Pest 4** with `Volt::test()` for Livewire components
- Every feature must have corresponding tests
- Use factories with custom states when creating test models
- Follow existing test structure in `tests/Feature/` and `tests/Unit/`

### Test Example

```php
use Livewire\Volt\Volt;

test('consultation list can be filtered by status', function () {
    $user = User::factory()->admin()->create();

    Volt::test('admin.consultations.index')
        ->actingAs($user)
        ->set('status', ConsultationStatus::Pending->value)
        ->assertHasNoErrors();
});
```

## Bilingual Support

- All user-facing strings must use Laravel's `__()` helper
- Translations stored in `resources/lang/{ar,en}/`
- Use JSON translations for simple strings, PHP arrays for structured data
- RTL layout handled via Tailwind CSS classes
