diff --git a/.cursor/hooks.json b/.cursor/hooks.json
new file mode 100644
index 0000000..2f93374
--- /dev/null
+++ b/.cursor/hooks.json
@@ -0,0 +1,10 @@
+{
+ "version": 1,
+ "hooks": {
+ "stop": [
+ {
+ "command": "npx tsx .cursor/hooks/run-phpstan-on-stop.ts"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/.cursor/hooks/run-phpstan-on-stop.ts b/.cursor/hooks/run-phpstan-on-stop.ts
new file mode 100644
index 0000000..609a8b6
--- /dev/null
+++ b/.cursor/hooks/run-phpstan-on-stop.ts
@@ -0,0 +1,90 @@
+///
+import { spawnSync } from "node:child_process";
+
+type StopHookInput = {
+ status?: "completed" | "aborted" | "error";
+};
+
+const COMMAND = {
+ tool: "phpstan",
+ cmd: "./vendor/bin/phpstan analyse --memory-limit=512M",
+};
+
+async function parseInput(): Promise {
+ const chunks: Buffer[] = [];
+ for await (const chunk of process.stdin) {
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
+ }
+ const text = Buffer.concat(chunks).toString("utf8");
+ try {
+ return JSON.parse(text) as StopHookInput;
+ } catch {
+ return {};
+ }
+}
+
+function hasGitChanges(): boolean {
+ const result = spawnSync("git", [
+ "status",
+ "--porcelain",
+ "--untracked-files",
+ ]);
+ return result.stdout.toString().trim().length > 0;
+}
+
+function runCommand(
+ _name: string,
+ cmd: string,
+): { ok: boolean; output: string } {
+ const result = spawnSync(cmd, {
+ stdio: ["inherit", "pipe", "pipe"],
+ shell: true,
+ });
+ const out = result.stdout?.toString() ?? "";
+ const err = result.stderr?.toString() ?? "";
+ const output = [out, err].filter(Boolean).join("\n").trim();
+ if (output) process.stderr.write(output);
+ return { ok: result.status === 0, output };
+}
+
+async function main(): Promise {
+ const input = await parseInput();
+
+ if (input.status !== "completed") {
+ process.stdout.write(JSON.stringify({}) + "\n");
+ return;
+ }
+
+ if (!hasGitChanges()) {
+ process.stdout.write(JSON.stringify({}) + "\n");
+ return;
+ }
+
+ const MAX_ERROR_CHARS = 1000;
+
+ const { ok, output } = runCommand(COMMAND.tool, COMMAND.cmd);
+ const failures = ok ? [] : [{ tool: COMMAND.tool, output }];
+
+ const truncatedOutput =
+ output && output.length > MAX_ERROR_CHARS
+ ? output.slice(0, MAX_ERROR_CHARS) + "\n… (truncated)"
+ : output || "(no output)";
+
+ const result =
+ failures.length > 0
+ ? {
+ followup_message: [
+ `Please fix the errors reported by ${COMMAND.tool}.`,
+ "Don't do any other investigation.",
+ "",
+ `<${COMMAND.tool}-errors>\n${truncatedOutput}\n${COMMAND.tool}-errors>`,
+ ].join("\n"),
+ }
+ : {};
+ process.stdout.write(JSON.stringify(result) + "\n");
+}
+
+main().catch((err) => {
+ console.error("[run-phpstan-on-stop]", err);
+ process.stdout.write(JSON.stringify({}) + "\n");
+});
diff --git a/.cursor/mcp.json b/.cursor/mcp.json
new file mode 100644
index 0000000..8c6715a
--- /dev/null
+++ b/.cursor/mcp.json
@@ -0,0 +1,11 @@
+{
+ "mcpServers": {
+ "laravel-boost": {
+ "command": "php",
+ "args": [
+ "artisan",
+ "boost:mcp"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/.cursor/rules/code-formatters.mdc b/.cursor/rules/code-formatters.mdc
new file mode 100644
index 0000000..44175fa
--- /dev/null
+++ b/.cursor/rules/code-formatters.mdc
@@ -0,0 +1,12 @@
+---
+alwaysApply: true
+---
+
+# Code formatters & refactors (auto-updating tools)
+
+These tools modify code automatically. Run them before finalizing changes so the codebase stays consistent.
+
+## Rector
+
+- Run `vendor/bin/rector --ansi` before finalizing changes so refactoring rules are applied.
+- Rector updates code on its own; run it to keep the codebase aligned with configured rules.
diff --git a/.cursor/rules/conventional-commits.mdc b/.cursor/rules/conventional-commits.mdc
new file mode 100644
index 0000000..07e61af
--- /dev/null
+++ b/.cursor/rules/conventional-commits.mdc
@@ -0,0 +1,66 @@
+---
+description: Use Conventional Commits format for all commit messages
+alwaysApply: true
+---
+
+# Conventional Commits
+
+## Format
+
+```
+[optional scope]:
+
+[optional body]
+
+[optional footer(s)]
+```
+
+## Types
+
+| Type | Use when |
+|------|----------|
+| `feat` | Adding a new feature (MINOR in SemVer) |
+| `fix` | Patching a bug (PATCH in SemVer) |
+| `docs` | Documentation only |
+| `style` | Formatting, whitespace, no code change |
+| `refactor` | Code change that neither fixes a bug nor adds a feature |
+| `perf` | Performance improvement |
+| `test` | Adding or updating tests |
+| `build` | Build system or dependencies |
+| `ci` | CI configuration |
+| `chore` | Other changes (maintenance, tooling) |
+
+## Scope
+
+Optional. Use a noun describing the affected area in parentheses: `feat(auth):`, `fix(api):`.
+
+## Examples
+
+```
+feat: add user registration endpoint
+```
+
+```
+fix(login): prevent redirect loop on session expiry
+```
+
+```
+chore(hooks): rename run-quality-checks to run-phpstan-on-stop
+```
+
+```
+docs: correct spelling of CHANGELOG
+```
+
+```
+feat(api)!: drop support for deprecated endpoints
+
+BREAKING CHANGE: /v1/users has been removed. Use /v2/users instead.
+```
+
+## Rules
+
+- Description is required and MUST immediately follow the type/scope.
+- Use imperative mood: "add feature" not "added feature".
+- No period at the end of the description.
+- Use `!` after type/scope or `BREAKING CHANGE:` in footer for breaking changes.
diff --git a/.cursor/rules/laravel-boost.mdc b/.cursor/rules/laravel-boost.mdc
new file mode 100644
index 0000000..99c77ff
--- /dev/null
+++ b/.cursor/rules/laravel-boost.mdc
@@ -0,0 +1,391 @@
+---
+alwaysApply: true
+---
+
+=== 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.5.2
+- filament/filament (FILAMENT) - v5
+- laravel/framework (LARAVEL) - v12
+- laravel/prompts (PROMPTS) - v0
+- livewire/livewire (LIVEWIRE) - v4
+- larastan/larastan (LARASTAN) - v3
+- laravel/mcp (MCP) - v0
+- 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` — Tests applications using the Pest 4 PHP framework. Activates when writing tests, creating unit or feature tests, adding assertions, testing Livewire components, browser testing, debugging test failures, working with datasets or mocking; or when the user mentions test, spec, TDD, expects, assertion, coverage, or needs to verify functionality works.
+- `tailwindcss-development` — Styles applications using Tailwind CSS v4 utilities. Activates when adding styles, restyling components, working with gradients, spacing, layout, flex, grid, responsive design, dark mode, colors, typography, or borders; or when the user mentions CSS, styling, classes, Tailwind, restyle, hero section, cards, buttons, or any visual/UI changes.
+
+## 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
+
+- Use the `list-artisan-commands` tool when you need to call an Artisan command to double-check the available 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.
+
+## Tinker / Debugging
+
+- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly.
+- Use the `database-query` tool when you only need to read from the database.
+
+## 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.
+
+
+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.
+
+=== herd rules ===
+
+# Laravel Herd
+
+- The application is served by Laravel Herd and will be available at: `https?://[kebab-case-project-dir].test`. Use the `get-absolute-url` tool to generate valid URLs for the user.
+- You must not run any commands to make the site available via HTTP(S). It is always available through Laravel Herd.
+
+=== 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 the `list-artisan-commands` tool.
+- 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 `list-artisan-commands` to check the available options to `php artisan make:model`.
+
+### 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
+
+- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style.
+- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` 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.
+- CRITICAL: ALWAYS use `search-docs` tool for version-specific Pest documentation and updated code examples.
+- IMPORTANT: Activate `pest-testing` every time you're working with a Pest or testing-related task.
+
+=== tailwindcss/core rules ===
+
+# Tailwind CSS
+
+- Always use existing Tailwind conventions; check project patterns before adding new ones.
+- IMPORTANT: Always use `search-docs` tool for version-specific Tailwind CSS documentation and updated code examples. Never rely on training data.
+- IMPORTANT: Activate `tailwindcss-development` every time you're working with a Tailwind CSS or styling-related task.
+
+=== filament/filament rules ===
+
+## Filament
+
+- Filament is used by this application. Follow existing conventions for how and where it's 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.
+
+### Artisan
+
+- Use Filament-specific Artisan commands to create files. Find them with `list-artisan-commands` or `php artisan --help`.
+- Inspect required options and always pass `--no-interaction`.
+
+### Patterns
+
+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:
+
+
+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'),
+
+
+
+Use `state()` with a `Closure` to compute derived column values:
+
+
+use Filament\Tables\Columns\TextColumn;
+
+TextColumn::make('full_name')
+ ->state(fn (User $record): string => "{$record->first_name} {$record->last_name}"),
+
+
+
+Actions encapsulate a button with optional modal form and logic:
+
+
+use Filament\Actions\Action;
+use Filament\Forms\Components\TextInput;
+
+Action::make('updateEmail')
+ ->form([
+ TextInput::make('email')->email()->required(),
+ ])
+ ->action(fn (array $data, User $record): void => $record->update($data)),
+
+
+
+### Testing
+
+Authenticate before testing panel functionality. Filament uses Livewire, so use `livewire()` or `Livewire::test()`:
+
+
+ livewire(ListUsers::class)
+ ->assertCanSeeTableRecords($users)
+ ->searchTable($users->first()->name)
+ ->assertCanSeeTableRecords($users->take(1))
+ ->assertCanNotSeeTableRecords($users->skip(1));
+
+
+
+
+ livewire(CreateUser::class)
+ ->fillForm([
+ 'name' => 'Test',
+ 'email' => 'test@example.com',
+ ])
+ ->call('create')
+ ->assertNotified()
+ ->assertRedirect();
+
+ assertDatabaseHas(User::class, [
+ 'name' => 'Test',
+ 'email' => 'test@example.com',
+ ]);
+
+
+
+
+ livewire(CreateUser::class)
+ ->fillForm([
+ 'name' => null,
+ 'email' => 'invalid-email',
+ ])
+ ->call('create')
+ ->assertHasFormErrors([
+ 'name' => 'required',
+ 'email' => 'email',
+ ])
+ ->assertNotNotified();
+
+
+
+
+ use Filament\Actions\DeleteAction;
+ use Filament\Actions\Testing\TestAction;
+
+ livewire(EditUser::class, ['record' => $user->id])
+ ->callAction(DeleteAction::class)
+ ->assertNotified()
+ ->assertRedirect();
+
+ livewire(ListUsers::class)
+ ->callAction(TestAction::make('promote')->table($user), [
+ 'role' => 'admin',
+ ])
+ ->assertNotified();
+
+
+
+### Common Mistakes
+
+**Commonly Incorrect Namespaces:**
+- Form fields (TextInput, Select, etc.): `Filament\Forms\Components\`
+- Infolist entries (for read-only views) (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: `Filament\Actions\` (no `Filament\Tables\Actions\` etc.)
+- Icons: `Filament\Support\Icons\Heroicon` enum (e.g., `Heroicon::PencilSquare`)
+
+**Recent breaking changes to Filament:**
+- File visibility is `private` by default. Use `->visibility('public')` for public access.
+- `Grid`, `Section`, and `Fieldset` no longer span all columns by default.
+
diff --git a/.cursor/skills/pest-testing/SKILL.md b/.cursor/skills/pest-testing/SKILL.md
new file mode 100644
index 0000000..67455e7
--- /dev/null
+++ b/.cursor/skills/pest-testing/SKILL.md
@@ -0,0 +1,174 @@
+---
+name: pest-testing
+description: >-
+ Tests applications using the Pest 4 PHP framework. Activates when writing tests, creating unit or feature
+ tests, adding assertions, testing Livewire components, browser testing, debugging test failures,
+ working with datasets or mocking; or when the user mentions test, spec, TDD, expects, assertion,
+ coverage, or needs to verify functionality works.
+---
+
+# Pest Testing 4
+
+## When to Apply
+
+Activate this skill when:
+
+- Creating new tests (unit, feature, or browser)
+- Modifying existing tests
+- Debugging test failures
+- Working with browser testing or smoke testing
+- Writing architecture tests or visual regression tests
+
+## Documentation
+
+Use `search-docs` for detailed Pest 4 patterns and documentation.
+
+## Basic Usage
+
+### Creating Tests
+
+All tests must be written using Pest. Use `php artisan make:test --pest {name}`.
+
+### Test Organization
+
+- Unit/Feature tests: `tests/Feature` and `tests/Unit` directories.
+- Browser tests: `tests/Browser/` directory.
+- Do NOT remove tests without approval - these are core application code.
+
+### Basic Test Structure
+
+
+
+it('is true', function () {
+ expect(true)->toBeTrue();
+});
+
+
+
+### Running Tests
+
+- Run minimal tests with filter before finalizing: `php artisan test --compact --filter=testName`.
+- Run all tests: `php artisan test --compact`.
+- Run file: `php artisan test --compact tests/Feature/ExampleTest.php`.
+
+## Assertions
+
+Use specific assertions (`assertSuccessful()`, `assertNotFound()`) instead of `assertStatus()`:
+
+
+
+it('returns all', function () {
+ $this->postJson('/api/docs', [])->assertSuccessful();
+});
+
+
+
+| Use | Instead of |
+|-----|------------|
+| `assertSuccessful()` | `assertStatus(200)` |
+| `assertNotFound()` | `assertStatus(404)` |
+| `assertForbidden()` | `assertStatus(403)` |
+
+## Mocking
+
+Import mock function before use: `use function Pest\Laravel\mock;`
+
+## Datasets
+
+Use datasets for repetitive tests (validation rules, etc.):
+
+
+
+it('has emails', function (string $email) {
+ expect($email)->not->toBeEmpty();
+})->with([
+ 'james' => 'james@laravel.com',
+ 'taylor' => 'taylor@laravel.com',
+]);
+
+
+
+## Pest 4 Features
+
+| Feature | Purpose |
+|---------|---------|
+| Browser Testing | Full integration tests in real browsers |
+| Smoke Testing | Validate multiple pages quickly |
+| Visual Regression | Compare screenshots for visual changes |
+| Test Sharding | Parallel CI runs |
+| Architecture Testing | Enforce code conventions |
+
+### Browser Test Example
+
+Browser tests run in real browsers for full integration testing:
+
+- Browser tests live in `tests/Browser/`.
+- Use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories.
+- Use `RefreshDatabase` for clean state per test.
+- Interact with page: click, type, scroll, select, submit, drag-and-drop, touch gestures.
+- Test on multiple browsers (Chrome, Firefox, Safari) if requested.
+- Test on different devices/viewports (iPhone 14 Pro, tablets) if requested.
+- Switch color schemes (light/dark mode) when appropriate.
+- Take screenshots or pause tests for debugging.
+
+
+
+it('may reset the password', function () {
+ Notification::fake();
+
+ $this->actingAs(User::factory()->create());
+
+ $page = visit('/sign-in');
+
+ $page->assertSee('Sign In')
+ ->assertNoJavaScriptErrors()
+ ->click('Forgot Password?')
+ ->fill('email', 'nuno@laravel.com')
+ ->click('Send Reset Link')
+ ->assertSee('We have emailed your password reset link!');
+
+ Notification::assertSent(ResetPassword::class);
+});
+
+
+
+### Smoke Testing
+
+Quickly validate multiple pages have no JavaScript errors:
+
+
+
+$pages = visit(['/', '/about', '/contact']);
+
+$pages->assertNoJavaScriptErrors()->assertNoConsoleLogs();
+
+
+
+### Visual Regression Testing
+
+Capture and compare screenshots to detect visual changes.
+
+### Test Sharding
+
+Split tests across parallel processes for faster CI runs.
+
+### Architecture Testing
+
+Pest 4 includes architecture testing (from Pest 3):
+
+
+
+arch('controllers')
+ ->expect('App\Http\Controllers')
+ ->toExtendNothing()
+ ->toHaveSuffix('Controller');
+
+
+
+## Common Pitfalls
+
+- Not importing `use function Pest\Laravel\mock;` before using mock
+- Using `assertStatus(200)` instead of `assertSuccessful()`
+- Forgetting datasets for repetitive validation tests
+- Deleting tests without approval
+- Forgetting `assertNoJavaScriptErrors()` in browser tests
\ No newline at end of file
diff --git a/.cursor/skills/tailwindcss-development/SKILL.md b/.cursor/skills/tailwindcss-development/SKILL.md
new file mode 100644
index 0000000..12bd896
--- /dev/null
+++ b/.cursor/skills/tailwindcss-development/SKILL.md
@@ -0,0 +1,124 @@
+---
+name: tailwindcss-development
+description: >-
+ Styles applications using Tailwind CSS v4 utilities. Activates when adding styles, restyling components,
+ working with gradients, spacing, layout, flex, grid, responsive design, dark mode, colors,
+ typography, or borders; or when the user mentions CSS, styling, classes, Tailwind, restyle,
+ hero section, cards, buttons, or any visual/UI changes.
+---
+
+# Tailwind CSS Development
+
+## When to Apply
+
+Activate this skill when:
+
+- Adding styles to components or pages
+- Working with responsive design
+- Implementing dark mode
+- Extracting repeated patterns into components
+- Debugging spacing or layout issues
+
+## Documentation
+
+Use `search-docs` for detailed Tailwind CSS v4 patterns and documentation.
+
+## Basic Usage
+
+- Use Tailwind CSS classes to style HTML. Check and follow existing Tailwind conventions in the project before introducing new patterns.
+- Offer to extract repeated patterns into components that match the project's conventions (e.g., Blade, JSX, Vue).
+- Consider class placement, order, priority, and defaults. Remove redundant classes, add classes to parent or child elements carefully to reduce repetition, and group elements logically.
+
+## Tailwind CSS v4 Specifics
+
+- Always use Tailwind CSS v4 and avoid deprecated utilities.
+- `corePlugins` is not supported in Tailwind v4.
+
+### CSS-First Configuration
+
+In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed:
+
+
+@theme {
+ --color-brand: oklch(0.72 0.11 178);
+}
+
+
+### Import Syntax
+
+In Tailwind v4, import Tailwind with a regular CSS `@import` statement instead of the `@tailwind` directives used in v3:
+
+
+- @tailwind base;
+- @tailwind components;
+- @tailwind utilities;
++ @import "tailwindcss";
+
+
+### Replaced Utilities
+
+Tailwind v4 removed deprecated utilities. Use the replacements shown below. Opacity values remain numeric.
+
+| Deprecated | Replacement |
+|------------|-------------|
+| bg-opacity-* | bg-black/* |
+| text-opacity-* | text-black/* |
+| border-opacity-* | border-black/* |
+| divide-opacity-* | divide-black/* |
+| ring-opacity-* | ring-black/* |
+| placeholder-opacity-* | placeholder-black/* |
+| flex-shrink-* | shrink-* |
+| flex-grow-* | grow-* |
+| overflow-ellipsis | text-ellipsis |
+| decoration-slice | box-decoration-slice |
+| decoration-clone | box-decoration-clone |
+
+## Spacing
+
+Use `gap` utilities instead of margins for spacing between siblings:
+
+
+
+
Item 1
+
Item 2
+
+
+
+## Dark Mode
+
+If existing pages and components support dark mode, new pages and components must support it the same way, typically using the `dark:` variant:
+
+
+