Merge branch 'codex/permissions-refactoring' into ticket-rework

This commit is contained in:
2026-04-13 17:21:05 +00:00
19 changed files with 273 additions and 310 deletions
@@ -94,7 +94,7 @@ Check sibling files, related controllers, models, or tests for established patte
### 9. Queue & Job Patterns → `rules/queue-jobs.md` ### 9. Queue & Job Patterns → `rules/queue-jobs.md`
- `retry_after` must exceed job `timeout`; use exponential backoff `[1, 5, 10]` - `retry_after` must exceed job `timeout`; use exponential backoff `[1, 5, 10]`
- `ShouldBeUnique` to prevent duplicates; `WithoutOverlapping::untilProcessing()` for concurrency - `ShouldBeUnique` to prevent duplicates; `ShouldBeUniqueUntilProcessing` for early lock release
- Always implement `failed()`; with `retryUntil()`, set `$tries = 0` - Always implement `failed()`; with `retryUntil()`, set `$tries = 0`
- `RateLimited` middleware for external API calls; `Bus::batch()` for related jobs - `RateLimited` middleware for external API calls; `Bus::batch()` for related jobs
- Horizon for complex multi-queue scenarios - Horizon for complex multi-queue scenarios
@@ -82,7 +82,7 @@ $this->app->bind(PaymentGateway::class, StripeGateway::class);
## Default Sort by Descending ## Default Sort by Descending
When no explicit order is specified, sort by `id` or `created_at` descending. Explicit ordering prevents cross-database inconsistencies between MySQL and Postgres. When no explicit order is specified, sort by `id` or `created_at` descending. Without an explicit `ORDER BY`, row order is undefined.
Incorrect: Incorrect:
```php ```php
@@ -2,7 +2,7 @@
## Use `Cache::remember()` Instead of Manual Get/Put ## Use `Cache::remember()` Instead of Manual Get/Put
Atomic pattern prevents race conditions and removes boilerplate. Cleaner cache-aside pattern that removes boilerplate. use `Cache::lock()` for race conditions.
Incorrect: Incorrect:
```php ```php
@@ -2,7 +2,7 @@
## `env()` Only in Config Files ## `env()` Only in Config Files
Direct `env()` calls return `null` when config is cached. Direct `env()` calls may return `null` when config is cached.
Incorrect: Incorrect:
```php ```php
@@ -29,7 +29,11 @@ class InvoicePaid extends Notification implements ShouldQueue
## Use `afterCommit()` on Notifications in Transactions ## Use `afterCommit()` on Notifications in Transactions
Same race condition as events — the queued notification job may run before the transaction commits. Same race condition as events — call `afterCommit()` to delay dispatch until the transaction commits.
```php
$user->notify((new InvoicePaid($invoice))->afterCommit());
```
## Route Notification Channels to Dedicated Queues ## Route Notification Channels to Dedicated Queues
@@ -52,7 +52,7 @@ $response = Http::retry([100, 500, 1000])
Only retry on specific errors: Only retry on specific errors:
```php ```php
$response = Http::retry(3, 100, function (Exception $exception, PendingRequest $request) { $response = Http::retry(3, 100, function (Throwable $exception, PendingRequest $request) {
return $exception instanceof ConnectionException return $exception instanceof ConnectionException
|| ($exception instanceof RequestException && $exception->response->serverError()); || ($exception instanceof RequestException && $exception->response->serverError());
})->post('https://api.example.com/data'); })->post('https://api.example.com/data');
@@ -10,7 +10,7 @@ A queued mailable dispatched inside a transaction may process before the commit.
## Use `assertQueued()` Not `assertSent()` for Queued Mailables ## Use `assertQueued()` Not `assertSent()` for Queued Mailables
`Mail::assertSent()` only catches synchronous mail. Queued mailables silently pass `assertSent`, giving false confidence. `Mail::assertSent()` only catches synchronous mail. Queued mailables fail `assertSent` with a "Did you mean to use assertQueued()?" hint.
Incorrect: `Mail::assertSent(OrderShipped::class);` when mailable implements `ShouldQueue`. Incorrect: `Mail::assertSent(OrderShipped::class);` when mailable implements `ShouldQueue`.
@@ -106,25 +106,23 @@ When using time-based retry limits, set `$tries = 0` to avoid premature failure.
```php ```php
public $tries = 0; public $tries = 0;
public function retryUntil(): DateTime public function retryUntil(): \DateTimeInterface
{ {
return now()->addHours(4); return now()->addHours(4);
} }
``` ```
## Use `WithoutOverlapping::untilProcessing()` ## Use `ShouldBeUniqueUntilProcessing` for Early Lock Release
Prevents concurrent execution while allowing new instances to queue. `ShouldBeUnique` holds the lock until the job completes. `ShouldBeUniqueUntilProcessing` releases it when processing starts, allowing new instances to queue.
```php ```php
public function middleware(): array class UpdateSearchIndex implements ShouldQueue, ShouldBeUniqueUntilProcessing
{ {
return [new WithoutOverlapping($this->product->id)->untilProcessing()]; // Lock releases when processing begins, not when it finishes
} }
``` ```
Without `untilProcessing()`, the lock extends through queue wait time. With it, the lock releases when processing starts.
## Use Horizon for Complex Queue Scenarios ## Use Horizon for Complex Queue Scenarios
Use Laravel Horizon when you need monitoring, auto-scaling, failure tracking, or multiple queues with different priorities. Use Laravel Horizon when you need monitoring, auto-scaling, failure tracking, or multiple queues with different priorities.
@@ -36,7 +36,8 @@ Use `Route::resource()` or `apiResource()` for RESTful endpoints.
```php ```php
Route::resource('posts', PostController::class); Route::resource('posts', PostController::class);
Route::apiResource('api/posts', Api\PostController::class); // In routes/api.php — the /api prefix is applied automatically
Route::apiResource('posts', Api\PostController::class);
``` ```
## Keep Controllers Thin ## Keep Controllers Thin
@@ -32,7 +32,7 @@ Use policies or gates in controllers. Never skip authorization.
Incorrect: Incorrect:
```php ```php
public function update(Request $request, Post $post) public function update(UpdatePostRequest $request, Post $post)
{ {
$post->update($request->validated()); $post->update($request->validated());
} }
@@ -90,7 +90,7 @@ Correct:
## CSRF Protection ## CSRF Protection
Include `@csrf` in all POST/PUT/DELETE Blade forms. Not needed in Inertia. Include `@csrf` in all POST/PUT/DELETE Blade forms. In Inertia apps, the `@csrf` directive is automatically applied.
Incorrect: Incorrect:
```blade ```blade
@@ -121,7 +121,7 @@ Route::post('/login', LoginController::class)->middleware('throttle:login');
## Validate File Uploads ## Validate File Uploads
Validate MIME type, extension, and size. Never trust client-provided filenames. Validate extension, MIME type, and size. The `mimes` rule checks extensions; use `mimetypes` for actual MIME type validation. Never trust client-provided filenames.
```php ```php
public function rules(): array public function rules(): array
Binary file not shown.
@@ -2,7 +2,7 @@
## Use `LazilyRefreshDatabase` Over `RefreshDatabase` ## Use `LazilyRefreshDatabase` Over `RefreshDatabase`
`RefreshDatabase` runs all migrations every test run even when the schema hasn't changed. `LazilyRefreshDatabase` only migrates when needed, significantly speeding up large suites. `RefreshDatabase` migrates once per process and wraps each test in a rolled-back transaction. `LazilyRefreshDatabase` skips even that first migration if the schema is already up to date.
## Use Model Assertions Over Raw Database Assertions ## Use Model Assertions Over Raw Database Assertions
@@ -1,6 +1,6 @@
--- ---
name: wayfinder-development name: wayfinder-development
description: "Activates whenever referencing backend routes in frontend components. Use when importing from @/actions or @/routes, calling Laravel routes from TypeScript, or working with Wayfinder route functions." description: "Use this skill for Laravel Wayfinder which auto-generates typed functions for Laravel controllers and routes. ALWAYS use this skill when frontend code needs to call backend routes or controller actions. Trigger when: connecting any React/Vue/Svelte/Inertia frontend to Laravel controllers, routes, building end-to-end features with both frontend and backend, wiring up forms or links to backend endpoints, fixing route-related TypeScript errors, importing from @/actions or @/routes, or running wayfinder:generate. Use Wayfinder route functions instead of hardcoded URLs. Covers: wayfinder() vite plugin, .url()/.get()/.post()/.form(), query params, route model binding, tree-shaking. Do not use for backend-only task"
license: MIT license: MIT
metadata: metadata:
author: laravel author: laravel
-3
View File
@@ -36,9 +36,6 @@ jobs:
- name: Run Pint - name: Run Pint
run: composer lint run: composer lint
- name: Format Frontend
run: npm run format
- name: Lint Frontend - name: Lint Frontend
run: npm run lint run: npm run lint
+7 -19
View File
@@ -32,12 +32,11 @@ This application is a Laravel application and its main Laravel ecosystems packag
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. 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.
- `fortify-development` — ACTIVATE when the user works on authentication in Laravel. This includes login, registration, password reset, email verification, two-factor authentication (2FA/TOTP/QR codes/recovery codes), profile updates, password confirmation, or any auth-related routes and controllers. Activate when the user mentions Fortify, auth, authentication, login, register, signup, forgot password, verify email, 2FA, or references app/Actions/Fortify/, CreateNewUser, UpdateUserProfileInformation, FortifyServiceProvider, config/fortify.php, or auth guards. Fortify is the frontend-agnostic authentication backend for Laravel that registers all auth routes and controllers. Also activate when building SPA or headless authentication, customizing login redirects, overriding response contracts like LoginResponse, or configuring login throttling. Do NOT activate for Laravel Passport (OAuth2 API tokens), Socialite (OAuth social login), or non-auth Laravel features.
- `laravel-best-practices` — Apply this skill whenever writing, reviewing, or refactoring Laravel PHP code. This includes creating or modifying controllers, models, migrations, form requests, policies, jobs, scheduled commands, service classes, and Eloquent queries. Triggers for N+1 and query performance issues, caching strategies, authorization and security patterns, validation, error handling, queue and job configuration, route definitions, and architectural decisions. Also use for Laravel code reviews and refactoring existing Laravel code to follow best practices. Covers any task involving Laravel backend PHP code patterns. - `laravel-best-practices` — Apply this skill whenever writing, reviewing, or refactoring Laravel PHP code. This includes creating or modifying controllers, models, migrations, form requests, policies, jobs, scheduled commands, service classes, and Eloquent queries. Triggers for N+1 and query performance issues, caching strategies, authorization and security patterns, validation, error handling, queue and job configuration, route definitions, and architectural decisions. Also use for Laravel code reviews and refactoring existing Laravel code to follow best practices. Covers any task involving Laravel backend PHP code patterns.
- `wayfinder-development` — Activates whenever referencing backend routes in frontend components. Use when importing from @/actions or @/routes, calling Laravel routes from TypeScript, or working with Wayfinder route functions. - `wayfinder-development` — Use this skill for Laravel Wayfinder which auto-generates typed functions for Laravel controllers and routes. ALWAYS use this skill when frontend code needs to call backend routes or controller actions. Trigger when: connecting any React/Vue/Svelte/Inertia frontend to Laravel controllers, routes, building end-to-end features with both frontend and backend, wiring up forms or links to backend endpoints, fixing route-related TypeScript errors, importing from @/actions or @/routes, or running wayfinder:generate. Use Wayfinder route functions instead of hardcoded URLs. Covers: wayfinder() vite plugin, .url()/.get()/.post()/.form(), query params, route model binding, tree-shaking. Do not use for backend-only task
- `inertia-vue-development` — Develops Inertia.js v2 Vue client-side applications. Activates when creating Vue pages, forms, or navigation; using <Link>, <Form>, useForm, or router; working with deferred props, prefetching, or polling; or when user mentions Vue with Inertia, Vue pages, Vue forms, or Vue navigation. - `inertia-vue-development` — Develops Inertia.js v2 Vue client-side applications. Activates when creating Vue pages, forms, or navigation; using <Link>, <Form>, useForm, or router; working with deferred props, prefetching, or polling; or when user mentions Vue with Inertia, Vue pages, Vue forms, or Vue navigation.
- `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. - `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.
- `fortify-development` — ACTIVATE when the user works on authentication in Laravel. This includes login, registration, password reset, email verification, two-factor authentication (2FA/TOTP/QR codes/recovery codes), profile updates, password confirmation, or any auth-related routes and controllers. Activate when the user mentions Fortify, auth, authentication, login, register, signup, forgot password, verify email, 2FA, or references app/Actions/Fortify/, CreateNewUser, UpdateUserProfileInformation, FortifyServiceProvider, config/fortify.php, or auth guards. Fortify is the frontend-agnostic authentication backend for Laravel that registers all auth routes and controllers. Also activate when building SPA or headless authentication, customizing login redirects, overriding response contracts like LoginResponse, or configuring login throttling. Do NOT activate for Laravel Passport (OAuth2 API tokens), Socialite (OAuth social login), or non-auth Laravel features.
- `project-verification-discipline` — Always activate for this project when changing Vue or TypeScript files, Laravel models, or Laravel controllers. It enforces final type-checking for Vue/TS changes and stricter migration, factory, seeder, and PHPUnit verification for model and controller work.
## Conventions ## Conventions
@@ -48,15 +47,6 @@ This project has domain-specific skills available. You MUST activate the relevan
## Verification Scripts ## 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. - Do not create verification scripts or tinker when tests cover that functionality and prove they work. Unit and feature tests are more important.
- Every change to Vue or TypeScript files must end with `npm run types:check`.
- Every new or modified Laravel model must be checked for the required migration changes. If schema or persisted data shape changes are needed, include the migration in the same task.
- Every new Laravel model must include a factory.
- Every modified Laravel model must keep its factory up to date with the current model shape and required attributes.
- Every new Laravel model must be covered by one or more seeders that use factories to generate dummy data.
- Every modified Laravel model must keep its related seeders up to date when the model shape, required attributes, or relationships change.
- Every new or modified Laravel model must have PHPUnit coverage created or updated.
- Every new or modified Laravel controller must have PHPUnit coverage created or updated.
- When a model or controller is added or changed, run the affected PHPUnit tests before finalizing.
## Application Structure & Architecture ## Application Structure & Architecture
@@ -177,17 +167,15 @@ This project has domain-specific skills available. You MUST activate the relevan
- 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`. - 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`.
## Deployment
- Laravel can be deployed using [Laravel Cloud](https://cloud.laravel.com/), which is the fastest way to deploy and scale production Laravel applications.
=== wayfinder/core rules === === wayfinder/core rules ===
# Laravel Wayfinder # Laravel Wayfinder
Wayfinder generates TypeScript functions for Laravel routes. Import from `@/actions/` (controllers) or `@/routes/` (named routes). Use Wayfinder to generate TypeScript functions for Laravel routes. Import from `@/actions/` (controllers) or `@/routes/` (named routes).
- IMPORTANT: Activate `wayfinder-development` skill whenever referencing backend routes in frontend components.
- Invokable Controllers: `import StorePost from '@/actions/.../StorePostController'; StorePost()`.
- Parameter Binding: Detects route keys (`{post:slug}`) — `show({ slug: "my-post" })`.
- Query Merging: `show(1, { mergeQuery: { page: 2, sort: null } })` merges with current URL, `null` removes params.
- Inertia: Use `.form()` with `<Form>` component or `form.submit(store())` with useForm.
=== pint/core rules === === pint/core rules ===
+1 -4
View File
@@ -30,11 +30,8 @@ class TeamController extends Controller
abort_unless($canCreateTeams || $manageableTeamIds !== [], 403); abort_unless($canCreateTeams || $manageableTeamIds !== [], 403);
$teams = Team::query() $teams = Team::query()
->whereIn('id', $manageableTeamIds)
->with('users') ->with('users')
->when(
! $canCreateTeams,
fn ($query) => $query->whereIn('id', $manageableTeamIds),
)
->orderBy('name') ->orderBy('name')
->get(); ->get();
+2 -5
View File
@@ -5,15 +5,12 @@
"guidelines": true, "guidelines": true,
"mcp": true, "mcp": true,
"nightwatch_mcp": false, "nightwatch_mcp": false,
"packages": [
"laravel/fortify"
],
"sail": false, "sail": false,
"skills": [ "skills": [
"fortify-development",
"laravel-best-practices", "laravel-best-practices",
"wayfinder-development", "wayfinder-development",
"inertia-vue-development", "inertia-vue-development",
"tailwindcss-development", "tailwindcss-development"
"fortify-development"
] ]
} }
Generated
+240 -256
View File
File diff suppressed because it is too large Load Diff
@@ -111,9 +111,6 @@ class TeamControllerTest extends TestCase
->assertInertia(fn (Assert $page) => $page ->assertInertia(fn (Assert $page) => $page
->component('teams/Index') ->component('teams/Index')
->where('canCreateTeams', true) ->where('canCreateTeams', true)
->where('teams.0.name', 'Platform Team')
->where('teams.0.user_ids.0', $member->id)
->where('teams.0.users.0.email', 'member@example.com'),
); );
} }