# Project Overview

Licentia is a **franchise-based business and licensing management system**. It handles businesses, license applications, document generation (quotes/invoices), financial tracking, and multi-franchise operations. Almost all application code lives inside the `Modules/` directory.

---

## Tech Stack

| Layer | Technology |
|---|---|
| Framework | Laravel 12 |
| PHP | 8.2+ |
| Admin / UI | Filament 4 |
| Reactivity | Livewire 3 |
| Modularity | nWidart Laravel Modules 12 |
| Database | MySQL 8.0 |
| CSS | Tailwind CSS 4.1 |
| Build | Vite 6 |
| PDF | DomPDF + Snappy (Chromium) + Browsershot |
| Email | Mailgun via Symfony Mailer |
| IMAP | DirectoryTree IMAP Engine |
| Excel | Maatwebsite Excel + PhpSpreadsheet |
| Queue | Database driver |
| Testing | PHPUnit 11.5 |
| Static Analysis | Larastan (PHPStan level 0) |
| Code Style | Laravel Pint (PSR-12 base) |

---

## Modules

All feature code lives in `Modules/`. Each module is self-contained with its own models, migrations, routes, services, Filament resources, and tests.

| Module | Prefix | Description |
|---|---|---|
| Support | `sp_` | Core utilities, helpers, base classes, TenantContext |
| Business | `bs_` | Business registration, mandates, clients |
| User | `usr_` | User management, authentication, client profiles |
| BookKeeping | `bk_` | Documents (quotes, invoices), expenses, financial categories |
| Finance | `fn_` | Bank accounts, franchise metrics |
| Item | `it_` | Product/service catalog |
| License | `ls_` | License applications and steps |
| Location | `lc_` | Franchises, provinces, towns, addresses |
| Mail | `ml_` | Email management, IMAP integration, delivery tracking |
| Mobile | — | Mobile app support (in progress) |

Table prefixes are defined in `config/module_table_name.php` and used via the `module_table()` helper function.

---

## Project Structure

```
Modules/
  {ModuleName}/
    Concerns/              <- Shared traits implementing interfaces
    Contracts/             <- Interfaces (e.g. CustomerInterface)
    Database/
      Migrations/          <- Numbered (000008_create_*.php)
      Migrations/ForeignKeys/  <- Separate FK constraint migrations
      Seeders/
      Factories/
    Enums/                 <- String-backed enums with Filament interfaces
    Events/
    Filament/
      Resources/
        {PluralName}/
          {ModelName}Resource.php
          Actions/         <- Custom Filament actions
          Pages/           <- List, Create, Edit, View pages
          Schemas/         <- Form and Infolist schema classes
          Tables/          <- Table configuration classes
          Widgets/         <- Resource-specific widgets
      Clusters/            <- Grouped resource clusters
    Helpers/
    Http/
      Controllers/
      Middleware/
    Listeners/
    Livewire/
    Models/
      Scopes/              <- Custom query scopes
    Notifications/
    Observers/
    Policies/
    Providers/
      Filament/            <- Panel providers (in Support module)
    Services/              <- Business logic classes
    Support/               <- Module-specific helpers
    Tests/
      Feature/
      Unit/
    Traits/
    routes/
      Web/web.php
      Api/api.php
    resources/views/
      livewire/
```

---

## Filament Panels

Two panels are configured in `Modules/Support/Providers/Filament/`:

- **MyFranchisePanelProvider** — Primary franchise-user panel
- **MyDeveloperPanelProvider** — Developer/admin panel

The root `/` route redirects to `/myFranchise/welcome`.

---

## Key Conventions

### art Command
- Use `art ...` insteaf of `art ...` as the locally hosted code runs inside a docker container

### Modules

- All new features go inside an existing or new module under `Modules/`
- Module names are **PascalCase** (e.g. `Modules/BookKeeping/`)
- Register new modules in `modules_statuses.json`
- Use `art module:make {Name}` to scaffold new modules
- Each module has its own service provider, routes, migrations, and models

### Models

- Located at `Modules/{ModuleName}/Models/{ModelName}.php`
- Never create models outside a module (except `app/Models/User.php` for auth if needed)
- **Class constants for every column name** — never use raw strings for column references:
  ```php
  const PRIMARY_KEY = 'business_id';
  const BUSINESS_REG_NUMBER = 'business_reg_number';
  const STATUS = 'status';

  protected $primaryKey = self::PRIMARY_KEY;
  protected $fillable = [self::BUSINESS_REG_NUMBER, self::STATUS, ...];
  protected $casts = [
      self::STATUS => BusinessStatus::class,
      self::CREATED_AT => 'datetime',
  ];
  ```
- Use `HasFactory` with `#[UseFactory(ModelFactory::class)]` attribute
- Use `SoftDeletes` on all models that require it
- Tenant scoping via `#[ScopedBy(OwnedByTenantScope::class)]` attribute
- Relationship methods use explicit return types (`BelongsTo`, `HasMany`, `BelongsToMany`, etc.)
- Reference related models via full `use` imports, never string class names
- Custom traits for shared behavior: `Notable`, `Deliverable`, `HasEntityEvents`, `IsRecipientOfUpload`
- Concerns implement interfaces (e.g. `IsBusinessCustomer` implements `CustomerInterface`)
- Old-style accessor methods (`getFullNameAttribute()`) are used alongside modern patterns

### Filament Resources

- Resources live in `Modules/{ModuleName}/Filament/Resources/{PluralName}/`
- The resource directory uses **plural naming** (e.g. `Businesses/BusinessResource.php`)
- **Delegate form/table/infolist to separate classes:**
  ```php
  public static function form(Schema $schema): Schema
  {
      return BusinessForm::configure($schema);
  }

  public static function table(Table $table): Table
  {
      return BusinessesTable::configure($table);
  }
  ```
- Schema classes in `Schemas/` (e.g. `BusinessForm`, `BusinessInfolist`)
- Table classes in `Tables/` (e.g. `BusinessesTable`)
- Page classes in `Pages/` (List, Create, Edit, View)
- Custom actions in `Actions/`
- Widgets in `Widgets/`
- Use `Filament\Schemas\Schema` (Filament 4 namespace), not the old `Forms\Form`
- Navigation icons use `Heroicon::OutlinedXxx` enum syntax
- Form components use `->live()`, `->afterStateUpdated(function (Set $set) { ... })` for reactivity
- Conditional visibility: `->visible(fn(Get $get) => ...)` and `->hidden(fn($context) => ...)`

### Enums

- String-backed enums implementing Filament interfaces:
  ```php
  enum DocumentType: string implements HasLabel, HasColor
  {
      case Quote = 'quote';
      case Invoice = 'invoice';

      public function getLabel(): string { ... }
      public function getColor(): string { ... }
  }
  ```
- Located in `Modules/{ModuleName}/Enums/`
- Cast in models via `self::STATUS => DocumentStatus::class`

### Services

- Named `{Resource}Service` (e.g. `DocumentService`, `BusinessService`)
- Located in `Modules/{ModuleName}/Services/`
- Encapsulate all business logic — controllers and Filament resources stay thin
- Use database transactions (`DB::transaction(...)`) for data consistency
- Dispatch events for side effects (notifications, delivery tracking)
- Resolve via the container: `app(DocumentService::class)`

### Custom Casts

- Domain-specific casts in module `Casts/` directory (e.g. `Cents` for money as integers)

### Routing

- Module routes in `Modules/{ModuleName}/routes/Web/web.php` and `Api/api.php`
- Loaded by each module's service provider
- Most UI routing is handled by Filament — web routes are minimal
- Route model binding used for traditional controllers
- API routes exist but are mostly commented out / in progress

### Migrations

- Located in `Modules/{ModuleName}/Database/Migrations/`
- **Numbered prefix convention**: `000001_`, `000002_`, etc.
- **Foreign keys in separate folder**: `Migrations/ForeignKeys/`
- Use `module_table("module_name", "table_name")` helper for table names (produces prefixed names like `bs_businesses`, `bk_documents`)
- Always include `$table->softDeletes()` where appropriate
- Use inline comments to explain business logic for nullable/complex columns
- Foreign key pattern:
  ```php
  $table->foreign('business_id')
      ->references('business_id')
      ->on(module_table("business", "businesses"))
      ->cascadeOnUpdate()
      ->cascadeOnDelete();
  ```

### Multi-tenancy

- Franchise-based tenancy via `TenantContext` singleton
- Access via `tenant()` helper (defined in `Modules/Support/Support/helper.php`)
- Global scope `OwnedByTenantScope` filters queries by franchise
- Applied via `#[ScopedBy(OwnedByTenantScope::class)]` attribute on models

### Livewire

- Components in `Modules/{ModuleName}/Livewire/`
- Views in `Modules/{ModuleName}/resources/views/livewire/`
- Use Livewire 3 syntax: `#[Attribute]` wire properties, `$this->dispatch()` for events
- Prefer `wire:model.live` over `wire:model.lazy`

### Events, Jobs, Notifications

- Events in `Modules/{ModuleName}/Events/`
- Listeners in `Modules/{ModuleName}/Listeners/`
- Event discovery is enabled in EventServiceProvider
- Jobs implement `ShouldQueue` with `Queueable` trait
- Notifications extend `Notification` with channel-specific delivery (database, mail)
- Mail classes extend a `BaseMail` abstract class with `build()` and abstract `getView()`
- Delivery tracking via `Delivery` model and `Channel` abstraction

### Observers

- Located in `Modules/{ModuleName}/Observers/`
- For model lifecycle event hooks
- Do not make use of observers, rather store logic inside the service class for the model

### Policies

- Located in `Modules/{ModuleName}/Policies/`
- Standard Laravel policy methods for authorization

### Controllers

- Located in `Modules/{ModuleName}/Http/Controllers/`
- Minimal — most UI is Filament-driven
- Return Blade views with data arrays
- Use route model binding
- Non-resource style with descriptive method names (`view()`, `edit()`, `review()`)
- No validation in controllers — use Filament forms or service layer

---

## Coding Standards

### PHP (enforced by Laravel Pint)

- **PSR-12** base (PER preset)
- **All classes must be `final`** (`final_class: true`)
- **Single quotes** for strings
- **Aligned `=` and `=>` operators** in assignments and arrays
- **Ordered class elements**: traits > cases > constants > properties > constructor > magic > static methods > public > protected > private
- **Ordered imports** alphabetically
- **No unused imports**
- **Trailing commas** in multiline arrays
- **No `else`/`elseif` when avoidable** — use early returns
- **`echo` over `print`**
- **`mb_str_functions`** — use multibyte string functions
- **Modernize**: `str_contains` over `strpos`, modern type casting
- **No arrow functions** (`use_arrow_functions: false`)
- **No `void` return declarations enforced** (`void_return: false`)
- **No `declare(strict_types=1)`** enforced (`declare_strict_types: false`)
- **Yoda style disabled** (`yoda_style: false`)
- Run with: `vendor/bin/pint` or `composer test` (which runs Pint via CI)

### PHP Static Analysis (Larastan)

- PHPStan **level 0** with baseline
- Analyzes `Modules/` directory only
- Run with: `vendor/bin/phpstan analyse`

### General

- Use **typed properties** and **return types** on all methods
- Prefer **early returns** over deeply nested conditionals
- Use **Form Request** classes or Filament forms for validation — never validate directly in controllers
- Do not use raw SQL — use Eloquent or Query Builder
- No `dd()` or `var_dump()` in committed code
- All user-facing strings should use `__()` for translation
- Reference model columns via class constants, not raw strings

---

## Testing

### Framework

PHPUnit 11.5 (not Pest)

### Structure

- Module tests in `Modules/{ModuleName}/Tests/Feature/` and `Tests/Unit/`
- Custom `TestCase` base class in `Modules/Support/Tests/TestCase.php` that:
  - Seeds the database if empty
  - Sets up authenticated user
  - Initializes `TenantContext`
- Use `RefreshDatabase` trait for test isolation
- PHPUnit `#[Test]` attribute for test methods (not `test_` prefix)
- Test method naming: `it_does_something_descriptive(): void`

### Patterns

```php
#[Test]
public function it_creates_a_quote_successfully(): void
{
    $this->seed();
    $this->actingAs($this->user);

    $service = app(DocumentService::class);
    $document = $service->create(...);

    $this->assertInstanceOf(Document::class, $document);
    $this->assertDatabaseHas('bk_documents', [...]);
}
```

### Running Tests

```bash
# Run all tests
art test
# or
composer test    # clears config cache first, then runs tests

# Run a specific module's tests
art test --filter=BookKeeping
```

---

## Common Commands

```bash
# Start dev server (app + queue + vite concurrently)
composer dev

# Run all migrations
art migrate

# Migrate a specific module
art module:migrate {ModuleName}

# Create a new module
art module:make {ModuleName}

# Create a model inside a module
art module:make-model {ModelName} {ModuleName}

# Seed a module
art module:seed {ModuleName}

# List all modules
art module:list

# Run tests
composer test

# Code style fix
vendor/bin/pint

# Static analysis
vendor/bin/phpstan analyse

# Clear all caches (local)
composer phpaargh

# Full rebuild (local) — clears caches + rebuilds frontend
composer phpgrrr

# Clear all caches (Sail/Docker)
composer aargh

# Full rebuild (Sail/Docker)
composer grrr
```

---

## CI/CD (GitHub Actions)

| Workflow | File | Trigger | Description |
|---|---|---|---|
| Code Quality | `code-quality.yml` | Manual dispatch | Runs Pint on changed PHP files, auto-commits fixes |
| Laravel PR | `laravel-pr.yml` | Manual dispatch | Full test suite with MySQL 8.0 service container |
| PHPStan | `phpstan.yml` | — | Static analysis |
| Pint | `pint.yml` | Manual dispatch | Code style check |
| Push to Main | `push-to-main.yml` | Push to main | Deployment pipeline |

Note: Most workflows are currently set to `workflow_dispatch` (manual trigger only).

---

## Helper Functions

Globally loaded via `composer.json` autoload `files` array:

| Helper | File | Purpose |
|---|---|---|
| `module_table($module, $table)` | `Modules/Support/Support/helper.php` | Returns prefixed table name (e.g. `module_table("business", "businesses")` returns `bs_businesses`) |
| `tenant()` | `Modules/Support/Support/helper.php` | Returns `TenantContext` singleton, auto-reloads if auth user changed |
| License helpers | `Modules/License/Support/Helper.php` | License-specific utilities |
| Finance helpers | `Modules/Finance/Support/Helper.php` | Finance-specific utilities |
| Business helpers | `Modules/Business/Support/helper.php` | Business-specific utilities |

---

## Important Rules

1. **Do not create files outside `Modules/`** unless there is a specific framework reason (e.g. root config, global service provider)
2. **Always use `module_table()` helper** when referencing table names in migrations and queries
3. **Always use model class constants** for column names — never raw strings
4. **Filament panel config** lives in `Modules/Support/Providers/Filament/` — one of the few things that references cross-module concerns
5. **The `app/Models/User.php` model** may exist for auth bootstrap — do not move unless asked
6. **Check `module.json`** inside each module for its registered name and namespace before making changes
7. **Enums must implement `HasLabel` and `HasColor`** from Filament when used in the UI
8. **Services own the business logic** — keep controllers and Filament resource pages thin
9. **Delegate Filament forms/tables/infolists** to separate schema and table classes, not inline in the resource
10. **Foreign key migrations go in a separate `ForeignKeys/` subdirectory** within the module's migrations folder

===

<laravel-boost-guidelines>
=== foundation rules ===

# Laravel Boost Guidelines

The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to ensure the best experience when building Laravel applications.

## Foundational Context

This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.

- php - 8.3
- filament/filament (FILAMENT) - v4
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
- livewire/livewire (LIVEWIRE) - v3
- larastan/larastan (LARASTAN) - v3
- laravel/boost (BOOST) - v2
- laravel/mcp (MCP) - v0
- laravel/pail (PAIL) - v1
- laravel/pint (PINT) - v1
- laravel/sail (SAIL) - v1
- pestphp/pest (PEST) - v4
- phpunit/phpunit (PHPUNIT) - v12
- rector/rector (RECTOR) - v2
- tailwindcss (TAILWINDCSS) - v4

## Skills Activation

This project has domain-specific skills available. You MUST activate the relevant skill whenever you work in that domain—don't wait until you're stuck.

- `pest-testing` — Use this skill for Pest PHP testing in Laravel projects only. Trigger whenever any test is being written, edited, fixed, or refactored — including fixing tests that broke after a code change, adding assertions, converting PHPUnit to Pest, adding datasets, and TDD workflows. Always activate when the user asks how to write something in Pest, mentions test files or directories (tests/Feature, tests/Unit, tests/Browser), or needs browser testing, smoke testing multiple pages for JS errors, or architecture tests. Covers: it()/expect() syntax, datasets, mocking, browser testing (visit/click/fill), smoke testing, arch(), Livewire component tests, RefreshDatabase, and all Pest 4 features. Do not use for factories, seeders, migrations, controllers, models, or non-test PHP code.
- `tailwindcss-development` — Always invoke when the user's message includes 'tailwind' in any form. Also invoke for: building responsive grid layouts (multi-column card grids, product grids), flex/grid page structures (dashboards with sidebars, fixed topbars, mobile-toggle navs), styling UI components (cards, tables, navbars, pricing sections, forms, inputs, badges), adding dark mode variants, fixing spacing or typography, and Tailwind v3/v4 work. The core use case: writing or fixing Tailwind utility classes in HTML templates (Blade, JSX, Vue). Skip for backend PHP logic, database queries, API routes, JavaScript with no HTML/CSS component, CSS file audits, build tool configuration, and vanilla CSS.

## Conventions

- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, and naming.
- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
- Check for existing components to reuse before writing a new one.

## Verification Scripts

- Do not create verification scripts or tinker when tests cover that functionality and prove they work. Unit and feature tests are more important.

## Application Structure & Architecture

- Stick to existing directory structure; don't create new base folders without approval.
- Do not change the application's dependencies without approval.

## Frontend Bundling

- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them.

## Documentation Files

- You must only create documentation files if explicitly requested by the user.

## Replies

- Be concise in your explanations - focus on what's important rather than explaining obvious details.

=== boost rules ===

# Laravel Boost

- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them.

## Artisan Commands

- Run Artisan commands directly via the command line (e.g., `php artisan route:list`, `php artisan tinker --execute "..."`).
- Use `php artisan list` to discover available commands and `php artisan [command] --help` to check parameters.

## URLs

- Whenever you share a project URL with the user, you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain/IP, and port.

## Debugging

- Use the `database-query` tool when you only need to read from the database.
- Use the `database-schema` tool to inspect table structure before writing migrations or models.
- To execute PHP code for debugging, run `php artisan tinker --execute "your code here"` directly.
- To read configuration values, read the config files directly or run `php artisan config:show [key]`.
- To inspect routes, run `php artisan route:list` directly.
- To check environment variables, read the `.env` file directly.

## Reading Browser Logs With the `browser-logs` Tool

- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost.
- Only recent browser logs will be useful - ignore old logs.

## Searching Documentation (Critically Important)

- Boost comes with a powerful `search-docs` tool you should use before trying other approaches when working with Laravel or Laravel ecosystem packages. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages.
- Search the documentation before making code changes to ensure we are taking the correct approach.
- Use multiple, broad, simple, topic-based queries at once. For example: `['rate limiting', 'routing rate limiting', 'routing']`. The most relevant results will be returned first.
- Do not add package names to queries; package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`.

### Available Search Syntax

1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'.
2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit".
3. Quoted Phrases (Exact Position) - query="infinite scroll" - words must be adjacent and in that order.
4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit".
5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms.

=== php rules ===

# PHP

- Always use curly braces for control structures, even for single-line bodies.

## Constructors

- Use PHP 8 constructor property promotion in `__construct()`.
    - `public function __construct(public GitHub $github) { }`
- Do not allow empty `__construct()` methods with zero parameters unless the constructor is private.

## Type Declarations

- Always use explicit return type declarations for methods and functions.
- Use appropriate PHP type hints for method parameters.

<!-- Explicit Return Types and Method Params -->
```php
protected function isAccessible(User $user, ?string $path = null): bool
{
    ...
}
```

## Enums

- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`.

## Comments

- Prefer PHPDoc blocks over inline comments. Never use comments within the code itself unless the logic is exceptionally complex.

## PHPDoc Blocks

- Add useful array shape type definitions when appropriate.

=== tests rules ===

# Test Enforcement

- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test --compact` with a specific filename or filter.

=== laravel/core rules ===

# Do Things the Laravel Way

- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using `php artisan list` and check their parameters with `php artisan [command] --help`.
- If you're creating a generic PHP class, use `php artisan make:class`.
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.

## Database

- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins.
- Use Eloquent models and relationships before suggesting raw database queries.
- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them.
- Generate code that prevents N+1 query problems by using eager loading.
- Use Laravel's query builder for very complex database operations.

### Model Creation

- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `php artisan make:model --help` to check the available options.

### APIs & Eloquent Resources

- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.

## Controllers & Validation

- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages.
- Check sibling Form Requests to see if the application uses array or string based validation rules.

## Authentication & Authorization

- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).

## URL Generation

- When generating links to other pages, prefer named routes and the `route()` function.

## Queues

- Use queued jobs for time-consuming operations with the `ShouldQueue` interface.

## Configuration

- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`.

## Testing

- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.

## Vite Error

- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`.

=== laravel/v12 rules ===

# Laravel 12

- CRITICAL: ALWAYS use `search-docs` tool for version-specific Laravel documentation and updated code examples.
- Since Laravel 11, Laravel has a new streamlined file structure which this project uses.

## Laravel 12 Structure

- In Laravel 12, middleware are no longer registered in `app/Http/Kernel.php`.
- Middleware are configured declaratively in `bootstrap/app.php` using `Application::configure()->withMiddleware()`.
- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files.
- `bootstrap/providers.php` contains application specific service providers.
- The `app/Console/Kernel.php` file no longer exists; use `bootstrap/app.php` or `routes/console.php` for console configuration.
- Console commands in `app/Console/Commands/` are automatically available and do not require manual registration.

## Database

- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost.
- Laravel 12 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`.

### Models

- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models.

=== pint/core rules ===

# Laravel Pint Code Formatter

- If you have modified any PHP files, you must run `vendor/bin/pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style.
- Do not run `vendor/bin/pint --test --format agent`, simply run `vendor/bin/pint --format agent` to fix any formatting issues.

=== pest/core rules ===

## Pest

- This project uses Pest for testing. Create tests: `php artisan make:test --pest {name}`.
- Run tests: `php artisan test --compact` or filter: `php artisan test --compact --filter=testName`.
- Do NOT delete tests without approval.

=== filament/filament rules ===

## Filament

- Filament is used by this application. Follow the existing conventions for how and where it is implemented.
- Filament is a Server-Driven UI (SDUI) framework for Laravel that lets you define user interfaces in PHP using structured configuration objects. Built on Livewire, Alpine.js, and Tailwind CSS.
- Use the `search-docs` tool for official documentation on Artisan commands, code examples, testing, relationships, and idiomatic practices. If `search-docs` is unavailable, refer to https://filamentphp.com/docs.

### Artisan

- Always use Filament-specific Artisan commands to create files. Find available commands with the `list-artisan-commands` tool, or run `php artisan --help`.
- Always inspect required options before running a command, and always pass `--no-interaction`.

### Patterns

Always use static `make()` methods to initialize components. Most configuration methods accept a `Closure` for dynamic values.

Use `Get $get` to read other form field values for conditional logic:

<code-snippet name="Conditional form field visibility" lang="php">
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Utilities\Get;

Select::make('type')
    ->options(CompanyType::class)
    ->required()
    ->live(),

TextInput::make('company_name')
    ->required()
    ->visible(fn (Get $get): bool => $get('type') === 'business'),

</code-snippet>

Use `state()` with a `Closure` to compute derived column values:

<code-snippet name="Computed table column value" lang="php">
use Filament\Tables\Columns\TextColumn;

TextColumn::make('full_name')
    ->state(fn (User $record): string => "{$record->first_name} {$record->last_name}"),

</code-snippet>

Actions encapsulate a button with an optional modal form and logic:

<code-snippet name="Action with modal form" lang="php">
use Filament\Actions\Action;
use Filament\Forms\Components\TextInput;

Action::make('updateEmail')
    ->schema([
        TextInput::make('email')
            ->email()
            ->required(),
    ])
    ->action(fn (array $data, User $record) => $record->update($data))

</code-snippet>

### Testing

Always authenticate before testing panel functionality. Filament uses Livewire, so use `Livewire::test()` or `livewire()` (available when `pestphp/pest-plugin-livewire` is in `composer.json`):

<code-snippet name="Table test" lang="php">
use function Pest\Livewire\livewire;

livewire(ListUsers::class)
    ->assertCanSeeTableRecords($users)
    ->searchTable($users->first()->name)
    ->assertCanSeeTableRecords($users->take(1))
    ->assertCanNotSeeTableRecords($users->skip(1));

</code-snippet>

<code-snippet name="Create resource test" lang="php">
use function Pest\Laravel\assertDatabaseHas;
use function Pest\Livewire\livewire;

livewire(CreateUser::class)
    ->fillForm([
        'name' => 'Test',
        'email' => 'test@example.com',
    ])
    ->call('create')
    ->assertNotified()
    ->assertRedirect();

assertDatabaseHas(User::class, [
    'name' => 'Test',
    'email' => 'test@example.com',
]);

</code-snippet>

<code-snippet name="Testing validation" lang="php">
use function Pest\Livewire\livewire;

livewire(CreateUser::class)
    ->fillForm([
        'name' => null,
        'email' => 'invalid-email',
    ])
    ->call('create')
    ->assertHasFormErrors([
        'name' => 'required',
        'email' => 'email',
    ])
    ->assertNotNotified();

</code-snippet>

<code-snippet name="Calling actions in pages" lang="php">
use Filament\Actions\DeleteAction;
use function Pest\Livewire\livewire;

livewire(EditUser::class, ['record' => $user->id])
    ->callAction(DeleteAction::class)
    ->assertNotified()
    ->assertRedirect();

</code-snippet>

<code-snippet name="Calling actions in tables" lang="php">
use Filament\Actions\Testing\TestAction;
use function Pest\Livewire\livewire;

livewire(ListUsers::class)
    ->callAction(TestAction::make('promote')->table($user), [
        'role' => 'admin',
    ])
    ->assertNotified();

</code-snippet>

### Correct Namespaces

- Form fields (`TextInput`, `Select`, etc.): `Filament\Forms\Components\`
- Infolist entries (`TextEntry`, `IconEntry`, etc.): `Filament\Infolists\Components\`
- Layout components (`Grid`, `Section`, `Fieldset`, `Tabs`, `Wizard`, etc.): `Filament\Schemas\Components\`
- Schema utilities (`Get`, `Set`, etc.): `Filament\Schemas\Components\Utilities\`
- Actions (`DeleteAction`, `CreateAction`, etc.): `Filament\Actions\`. Never use `Filament\Tables\Actions\`, `Filament\Forms\Actions\`, or any other sub-namespace for actions.
- Icons: `Filament\Support\Icons\Heroicon` enum (e.g., `Heroicon::PencilSquare`)

### Common Mistakes

- **Never assume public file visibility.** File visibility is `private` by default. Always use `->visibility('public')` when public access is needed.
- **Never assume full-width layout.** `Grid`, `Section`, and `Fieldset` do not span all columns by default. Explicitly set column spans when needed.

</laravel-boost-guidelines>
