# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview This is a **Laravel application** bootstrapped with [CleaniqueCoders Kickoff](https://github.com/cleaniquecoders/kickoff), providing a standardized structure with pre-configured packages and conventions. - **Framework**: Laravel 13+ with PHP 8.4+ - **Frontend**: Livewire 4 + TailwindCSS v4 + Alpine.js - **Testing**: Pest PHP (not PHPUnit syntax) - **Database**: MySQL with dual-key pattern (auto-increment `id` for internal relations + `uuid` column for public-facing identifiers) ### Architecture decisions (ADRs) Significant architectural choices are recorded as ADRs under [`docs/adr/`](docs/adr/). Read the relevant ADR before changing core abstractions (identity, money, audit, identifiers, swappable drivers). See [`docs/adr/README.md`](docs/adr/README.md) for the template and when to write one. ## Common Commands ```bash # Development composer dev # Start server, queue, logs, and Vite concurrently npm run dev # Vite dev server with HMR npm run build # Build production assets # Testing composer test # Run the full suite (Xdebug pinned off) composer test-parallel # Full suite in parallel composer test-arch # Architecture tests only composer test-tia # Fast loop — only tests impacted by your changes (needs pcov) composer test-tia-fresh # Discard the TIA graph and re-record composer test-coverage # Run tests with coverage (needs pcov) # Code Quality composer format # Format code with Laravel Pint composer analyse # Run PHPStan static analysis composer rector # Run Rector for automated refactoring composer lint # Check PHP syntax # Database php artisan migrate # Run migrations php artisan reload:db # Drop, migrate, and seed (fresh start) # Single test file ./vendor/bin/pest tests/Feature/ExampleTest.php ./vendor/bin/pest --filter="test name" ``` ## Architecture & Key Concepts ### Models - CRITICAL **ALL models MUST extend `App\Models\Base`** instead of `Illuminate\Database\Eloquent\Model`. Laravel 13 uses **PHP attributes** for model properties instead of `$fillable`, `$guarded`, `$hidden`: ```php namespace App\Models; use App\Models\Base as Model; use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Hidden; #[Fillable(['name', 'description', 'status'])] #[Hidden(['id'])] class Product extends Model { // Auto-increment id (internal) + uuid column (public-facing) - automatic // Auditing - automatic // Media support - automatic } ``` The Base model provides: - Dual-key pattern: auto-increment `id` + auto-generated `uuid` column (`InteractsWithUuid`) - Auditing via owen-it/laravel-auditing - Media attachments via Spatie Media Library - User tracking (created_by, updated_by) - Resource route helpers ### Database Conventions - **Primary keys**: Auto-increment `id` for internal DB relations + `uuid` column for public-facing identifiers (`$table->id()` + `$table->uuid('uuid')->index()`) - **Soft deletes**: Use for all user-facing models - **Column naming**: snake_case - **Credentials columns**: Always cast with `encrypted:array` (not manual `encrypt()`) > **Gotcha:** Using `encrypt()` manually when the model already has `encrypted:array` cast > causes double-encryption. The cast handles encryption transparently — just assign the plain array. ### Enums Use enums for all status/type fields. Place in `app/Enums/`. Custom stub at `stubs/enum.stub` generates the correct template via `php artisan make:enum`. ```php namespace App\Enums; use CleaniqueCoders\Traitify\Contracts\Enum as Contract; use CleaniqueCoders\Traitify\Concerns\InteractsWithEnum; enum Status: string implements Contract { use InteractsWithEnum; case DRAFT = 'draft'; case ACTIVE = 'active'; public function label(): string { return match ($this) { self::DRAFT => 'Draft', self::ACTIVE => 'Active', }; } public function description(): string { return match ($this) { self::DRAFT => 'Item is in draft state.', self::ACTIVE => 'Item is active.', }; } } ``` All enums must implement `CleaniqueCoders\Traitify\Contracts\Enum` and use the `InteractsWithEnum` trait. This provides `values()`, `labels()`, and `options()` methods. ### Authorization Use **Spatie Laravel Permission** with policies: ```php // Permission naming: module.action.target $user->can('users.view.list'); $user->can('products.create.item'); // In controllers $this->authorize('update', $product); ``` Default roles: `superadmin`, `administrator`, `user` ### Application Settings (Spatie Laravel Settings) Application-level settings are stored in the **database** via `spatie/laravel-settings` — NOT in `.env`. **Settings classes** in `app/Settings/`: - `GeneralSettings` — `site_name` - `MailSettings` — `from_address`, `from_name` - `NotificationSettings` — `enabled`, `channels` - `SeoSettings` — meta defaults, Open Graph, canonical toggle, robots.txt, GA4/GTM IDs, organization schema **How it works**: `AppServiceProvider::boot()` reads from DB and overrides `config()` values, so all existing `config('app.name')`, `config('mail.from.*')`, `config('notification.*')` calls automatically use DB values. ```php // Reading (via config — already overridden at runtime) config('app.name'); // Reading (via Settings class directly) app(GeneralSettings::class)->site_name; // Writing $settings = app(GeneralSettings::class); $settings->site_name = 'New Name'; $settings->save(); ``` **Admin UI**: Managed at Admin > Settings (site name, mail from, notifications). **What stays in .env**: Infrastructure settings (`APP_ENV`, `APP_DEBUG`, SMTP credentials, DB, Redis). > **Gotcha:** Never write to `.env` at runtime. Use Spatie Settings for any value that admins should be able to change from the UI. ### SEO & Analytics All SEO surface is admin-editable at **Admin > Settings > SEO & Analytics** (`SeoSettings`, overlaid onto `config('seo.*')` at boot). See `docs/02-development/17-seo.md` for the full guide. - Meta/OG/Twitter/canonical tags render from `partials/seo.blade.php` (included by `partials/head.blade.php` in every layout). Per-page override: ``. - GA4/GTM snippets (`partials/analytics.blade.php` + `analytics-noscript.blade.php`) render **only when an ID is set** — never hardcode tracking snippets in views. - `/robots.txt` and `/sitemap.xml` are routes in `routes/web/seo.php` — do NOT create static `public/robots.txt`/`public/sitemap.xml` by hand (a static file shadows the route). For big sites, schedule `php artisan seo:generate-sitemap`. - Structured data: `Organization` + `WebSite` schemas render automatically; page-level schemas use the `seo_schema_*()` helpers in `support/seo.php` (breadcrumb, faq, article, product, course, event, webpage). Do NOT hand-write `