chore(cursor): add Cursor rules for Laravel and frontend stacks
Ship .cursor/rules MDC files covering project context, Laravel patterns, Boost tooling, PHP style, Inertia v3/pagination, Vue/TypeScript, Pest, and Dusk. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
5f40b7bd6a
commit
4c9243ccb9
9 changed files with 446 additions and 0 deletions
26
.cursor/rules/inertia-pagination.mdc
Normal file
26
.cursor/rules/inertia-pagination.mdc
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
description: Pagination conventions — backend (Inertia scroll) and frontend (InfiniteScroll)
|
||||
globs: **/*.{php,vue,ts}
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Pagination
|
||||
|
||||
- Always use normal pagination (`->paginate()`). NEVER use `->cursorPaginate()`.
|
||||
- All paginated lists must use Inertia's scroll pagination: `Inertia::scroll()` on the backend with `<InfiniteScroll>` on the frontend. NEVER use traditional page-based pagination with page links/buttons.
|
||||
|
||||
```php
|
||||
// GOOD — backend
|
||||
return Inertia::render('Posts/Index', [
|
||||
'posts' => Inertia::scroll(fn () => PostResource::collection(
|
||||
Post::query()->latest()->paginate(20)
|
||||
)),
|
||||
]);
|
||||
```
|
||||
|
||||
```vue
|
||||
<!-- GOOD — frontend -->
|
||||
<InfiniteScroll :data="posts">
|
||||
<PostCard v-for="post in posts.data" :key="post.id" :post="post" />
|
||||
</InfiniteScroll>
|
||||
```
|
||||
33
.cursor/rules/inertia-v3.mdc
Normal file
33
.cursor/rules/inertia-v3.mdc
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
description: Inertia.js v3 — pages, props, SSR, breaking changes, deferred props patterns
|
||||
globs: **/*.{vue,ts,php}
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Inertia v3
|
||||
|
||||
Inertia creates fully client-side rendered SPAs without modern SPA complexity, leveraging existing server-side patterns.
|
||||
|
||||
- Components live in `resources/js/pages` (unless overridden in `vite.config.js`). Use `Inertia::render()` for server-side routing instead of Blade views.
|
||||
- ALWAYS use the `search-docs` Boost tool for version-specific Inertia documentation and updated code examples.
|
||||
- Activate the `inertia-vue-development` skill when working with Inertia Vue client-side patterns.
|
||||
|
||||
## v3 features
|
||||
|
||||
- Use all Inertia features from v1, v2, and v3. Check the documentation before making changes to ensure the correct approach.
|
||||
- New in v3: standalone HTTP requests (`useHttp` hook), optimistic updates with automatic rollback, layout props (`useLayoutProps` hook), instant visits, simplified SSR via `@inertiajs/vite` plugin, custom exception handling for error pages.
|
||||
- Carried over from v2: deferred props, infinite scroll, merging props, polling, prefetching, once props, flash data.
|
||||
|
||||
## Deferred props
|
||||
|
||||
When using deferred props, add an empty state with a pulsing or animated skeleton.
|
||||
|
||||
## Breaking changes from earlier versions
|
||||
|
||||
- Axios has been removed. Use the built-in XHR client with interceptors, or install Axios separately if needed.
|
||||
- `Inertia::lazy()` / `LazyProp` has been removed. Use `Inertia::optional()` instead.
|
||||
- Prop types (`Inertia::optional()`, `Inertia::defer()`, `Inertia::merge()`) work inside nested arrays with dot-notation paths.
|
||||
- SSR works automatically in Vite dev mode with `@inertiajs/vite` — no separate Node.js server needed during development.
|
||||
- Event renames: `invalid` is now `httpException`, `exception` is now `networkError`.
|
||||
- `router.cancel()` is replaced by `router.cancelAll()`.
|
||||
- The `future` configuration namespace has been removed — all v2 future options are now always enabled.
|
||||
47
.cursor/rules/laravel-boost-tools.mdc
Normal file
47
.cursor/rules/laravel-boost-tools.mdc
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
description: Laravel Boost MCP tools, search-docs usage, Artisan and Tinker conventions
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Laravel Boost Tools
|
||||
|
||||
Laravel Boost is an MCP server with tools tuned for this application. Prefer Boost tools over manual shell/file alternatives.
|
||||
|
||||
## Tools to prefer
|
||||
|
||||
- `database-query` — run read-only queries against the database instead of writing raw SQL in tinker.
|
||||
- `database-schema` — inspect table structure before writing migrations or models.
|
||||
- `get-absolute-url` — resolve the correct scheme/domain/port for project URLs. Always use this before sharing a URL.
|
||||
- `browser-logs` — read browser logs, errors, exceptions. Only recent entries are useful.
|
||||
|
||||
## search-docs (IMPORTANT)
|
||||
|
||||
- Always call `search-docs` before making code changes. It returns version-specific docs for installed packages automatically.
|
||||
- Pass a `packages` array to scope results when you know which packages are relevant.
|
||||
- Use multiple broad, topic-based queries: `['rate limiting', 'routing rate limiting', 'routing']`.
|
||||
- Do not add package names to queries; package info is already shared. Use `test resource table`, not `filament 4 test resource table`.
|
||||
|
||||
### Query syntax
|
||||
|
||||
1. Words = auto-stemmed AND logic: `rate limit` matches both "rate" AND "limit".
|
||||
2. `"quoted phrases"` = exact position matching.
|
||||
3. Mix: `middleware "rate limit"`.
|
||||
4. OR logic: pass multiple queries — `queries=["authentication", "middleware"]`.
|
||||
|
||||
## Artisan
|
||||
|
||||
- Run commands directly: `php artisan route:list`. Discover with `php artisan list`, inspect with `php artisan [command] --help`.
|
||||
- Filter routes: `--method=GET`, `--name=users`, `--path=api`, `--except-vendor`, `--only-vendor`.
|
||||
- Read config with dot notation: `php artisan config:show app.name`. Or read files in `config/`.
|
||||
- Read environment variables directly from `.env`.
|
||||
|
||||
## Tinker
|
||||
|
||||
- Use single quotes around the snippet to prevent shell expansion: `php artisan tinker --execute 'Your::code();'`.
|
||||
- Use double quotes for PHP strings inside: `php artisan tinker --execute 'User::where("active", true)->count();'`.
|
||||
- Do not create models via tinker without user approval — prefer tests with factories.
|
||||
|
||||
## Herd
|
||||
|
||||
- Site is served by Laravel Herd at `https?://[kebab-case-project-dir].test`. Use `get-absolute-url` for valid URLs. Never run commands to serve the site.
|
||||
- Use the `herd` CLI for services, PHP versions and sites (`herd sites`, `herd services:start <service>`, `herd php:list`). `herd list` shows all commands.
|
||||
63
.cursor/rules/laravel-patterns.mdc
Normal file
63
.cursor/rules/laravel-patterns.mdc
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
description: Laravel patterns — Artisan generators, models, APIs, routes, validation, deployment
|
||||
globs: **/*.php
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Laravel Patterns
|
||||
|
||||
## Do things the Laravel way
|
||||
|
||||
- Use `php artisan make:` commands to create new files (migrations, controllers, models, etc.). Discover via `php artisan list`, check options with `--help`.
|
||||
- For a generic PHP class, use `php artisan make:class`.
|
||||
- Pass `--no-interaction` to all Artisan commands. Also pass the correct `--options` to ensure correct behavior.
|
||||
|
||||
## Model creation
|
||||
|
||||
When creating new models, also create useful factories and seeders. Ask the user if they need anything else (use `php artisan make:model --help`).
|
||||
|
||||
## APIs & Eloquent Resources
|
||||
|
||||
For APIs, default to Eloquent API Resources and API versioning — unless existing routes already deviate, in which case follow the existing convention.
|
||||
|
||||
## URL generation
|
||||
|
||||
When generating links between pages, prefer named routes and the `route()` function.
|
||||
|
||||
## Backend validation
|
||||
|
||||
Validation rules always live in a dedicated `Illuminate\Foundation\Http\FormRequest` subclass under `app/Http/Requests/App/<Group>/`. Controller actions must type-hint the FormRequest as the parameter.
|
||||
|
||||
NEVER call `$request->validate([...])` inline in the controller.
|
||||
|
||||
Naming: `<Verb><Resource>Request.php` — `StorePostRequest`, `ApplyPostTemplateRequest`, `IndexPostTemplateRequest`.
|
||||
|
||||
```php
|
||||
// BAD
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate(['title' => 'required']);
|
||||
// ...
|
||||
}
|
||||
|
||||
// GOOD
|
||||
public function store(StorePostRequest $request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
- When creating models for tests, use factories. Check for custom states before manually setting up the model.
|
||||
- Faker: use `$this->faker->word()` or `fake()->randomDigit()` — follow existing convention in the file.
|
||||
- Create tests via `php artisan make:test [options] {name}`. Use `--unit` for unit tests. Most tests should be feature tests.
|
||||
|
||||
## Vite error
|
||||
|
||||
If you hit `Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest`, ask the user to run `npm run dev` or `composer run dev`, or run `npm run build`.
|
||||
|
||||
## Deployment
|
||||
|
||||
Laravel can be deployed using [Laravel Cloud](https://cloud.laravel.com/) — the fastest path to production for Laravel apps.
|
||||
77
.cursor/rules/php-style.mdc
Normal file
77
.cursor/rules/php-style.mdc
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
description: PHP coding style — control structures, types, imports, interpolation, status codes
|
||||
globs: **/*.php
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# PHP Style
|
||||
|
||||
- Always use curly braces for control structures, even for single-line bodies.
|
||||
- Use PHP 8 constructor property promotion: `public function __construct(public GitHub $github) {}`. Don't leave empty zero-parameter `__construct()` methods unless the constructor is private.
|
||||
- Always use explicit return types and parameter type hints: `function isAccessible(User $user, ?string $path = null): bool`.
|
||||
- Use TitleCase for Enum keys: `FavoritePerson`, `BestLake`, `Monthly`.
|
||||
- Prefer PHPDoc blocks over inline comments. Only add inline comments for exceptionally complex logic.
|
||||
- Use array shape type definitions in PHPDoc blocks.
|
||||
|
||||
## Imports
|
||||
|
||||
NEVER use inline class references. Always import at the top with `use`.
|
||||
|
||||
```php
|
||||
// BAD
|
||||
\DB::listen(...);
|
||||
\Str::uuid();
|
||||
|
||||
// GOOD
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
DB::listen(...);
|
||||
Str::uuid();
|
||||
```
|
||||
|
||||
## String interpolation
|
||||
|
||||
Prefer double-quoted interpolation with curly braces over concatenation with `.`.
|
||||
|
||||
```php
|
||||
// BAD
|
||||
'workspace.'.$workspace->id
|
||||
|
||||
// GOOD
|
||||
"workspace.{$workspace->id}"
|
||||
```
|
||||
|
||||
Single quotes are still preferred when the string has no interpolation. Always wrap interpolated variables in `{}` even for simple variables — keeps the boundary explicit and supports object/array access.
|
||||
|
||||
## HTTP status codes in JSON responses
|
||||
|
||||
Always use `Symfony\Component\HttpFoundation\Response` constants instead of magic numbers.
|
||||
|
||||
```php
|
||||
// BAD
|
||||
return response()->json($data, 201);
|
||||
|
||||
// GOOD
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
return response()->json($data, Response::HTTP_CREATED);
|
||||
```
|
||||
|
||||
## Array data access in Actions / services
|
||||
|
||||
Use `data_get()` instead of direct array access.
|
||||
|
||||
```php
|
||||
// BAD
|
||||
$name = $data['name'];
|
||||
$username = $data['username'] ?? $sender->username;
|
||||
|
||||
// GOOD
|
||||
$name = data_get($data, 'name');
|
||||
$username = data_get($data, 'username', $sender->username);
|
||||
```
|
||||
|
||||
## Pint formatting
|
||||
|
||||
After modifying any PHP file, run `vendor/bin/pint --dirty --format agent` before finalizing changes. Never run `--test`; just run the formatter and let it fix issues.
|
||||
56
.cursor/rules/project-context.mdc
Normal file
56
.cursor/rules/project-context.mdc
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
description: Foundational context, package versions, conventions, replies, docs and git rules
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# TryPost — Project Context
|
||||
|
||||
Laravel + Inertia v3 + Vue 3 + Tailwind v4 application. You are an expert on these specific package versions:
|
||||
|
||||
- php 8.4, laravel/framework v13
|
||||
- inertiajs/inertia-laravel v3, @inertiajs/vue3 v3
|
||||
- laravel/cashier v16, laravel/horizon v5, laravel/passport v13
|
||||
- laravel/pennant v1, laravel/reverb v1, laravel/socialite v5
|
||||
- laravel/wayfinder v0, laravel/ai v0, laravel/boost v2, laravel/mcp v0
|
||||
- laravel/nightwatch v1, laravel/telescope v5, laravel/pail v1, laravel/pint v1, laravel/sail v1
|
||||
- laravel/prompts v0
|
||||
- pestphp/pest v4, phpunit/phpunit v12
|
||||
- vue v3, tailwindcss v4, @laravel/echo-vue v2, laravel-echo v2
|
||||
- @laravel/vite-plugin-wayfinder v0
|
||||
- eslint v9, prettier v3
|
||||
|
||||
Documentation for end users lives at https://docs.trypost.it.
|
||||
|
||||
## Skills activation
|
||||
|
||||
This project has domain-specific skills in `.claude/skills/` (e.g. `pest-testing`, `inertia-vue-development`, `wayfinder-development`, `laravel-best-practices`, `cashier-stripe-development`, `mcp-development`, `passport-development`, `pennant-development`, `socialite-development`, `medialibrary-development`, `ai-sdk-development`, `configuring-horizon`, `configure-nightwatch`, `tailwindcss-development`, `upgrade-laravel-v13`, `humanizer`). Activate the relevant skill whenever you work in that domain — don't wait until you're stuck.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Follow existing code conventions. Check sibling files for structure, approach and naming before creating or editing.
|
||||
- Use descriptive names (`isRegisteredForDiscounts`, not `discount()`).
|
||||
- Reuse existing components before writing new ones.
|
||||
- Stick to existing directory structure. Do not create new base folders without approval.
|
||||
- Do not change dependencies without approval.
|
||||
|
||||
## Verification
|
||||
|
||||
- Do not create verification scripts or ad-hoc tinker code when tests cover the functionality. Prefer feature/unit tests.
|
||||
|
||||
## Documentation files
|
||||
|
||||
- Only create documentation files (`*.md`, READMEs) if explicitly requested by the user.
|
||||
|
||||
## Frontend bundling
|
||||
|
||||
- If the user doesn't see a frontend change reflected in the UI, ask them to run `npm run build`, `npm run dev`, or `composer run dev`.
|
||||
|
||||
## Replies
|
||||
|
||||
- Be concise. Focus on what matters, skip obvious explanations.
|
||||
|
||||
## Git
|
||||
|
||||
- NEVER add `Co-Authored-By` lines to commit messages.
|
||||
- NEVER commit, push, or open PRs unless the user explicitly asks.
|
||||
- Always create a new branch for feature work before making changes.
|
||||
43
.cursor/rules/tests-dusk.mdc
Normal file
43
.cursor/rules/tests-dusk.mdc
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
description: Laravel Dusk browser tests — named routes, dusk selectors, no CSS/text-based assertions
|
||||
globs: tests/Browser/**/*.php
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Dusk Browser Tests
|
||||
|
||||
## Named routes (IMPORTANT)
|
||||
|
||||
ALWAYS use named routes via the `route()` helper. NEVER hardcode URLs like `'https://trypost.test/login'`.
|
||||
|
||||
```php
|
||||
// BAD
|
||||
$browser->visit('https://trypost.test/login');
|
||||
|
||||
// GOOD
|
||||
$browser->visit(route('login'));
|
||||
```
|
||||
|
||||
## Dusk selectors
|
||||
|
||||
ALWAYS use `dusk` selectors (`@selector-name`) for interactions and assertions. NEVER use CSS classes (`.text-red-600`), tag names, or text strings.
|
||||
|
||||
Add `dusk="my-element"` attributes to Vue components and target them with `$browser->click('@my-element')`, `$browser->waitFor('@my-element')`, etc.
|
||||
|
||||
```php
|
||||
// BAD
|
||||
$browser->waitFor('.text-red-600');
|
||||
$browser->click('button.primary');
|
||||
$browser->assertSee('Welcome');
|
||||
|
||||
// GOOD
|
||||
$browser->waitFor('@input-error');
|
||||
$browser->click('@submit-button');
|
||||
$browser->assertVisible('@welcome-message');
|
||||
```
|
||||
|
||||
```vue
|
||||
<!-- Vue component -->
|
||||
<button dusk="submit-button" @click="handleSubmit">Submit</button>
|
||||
<span dusk="input-error" v-if="error">{{ error }}</span>
|
||||
```
|
||||
36
.cursor/rules/tests-pest.mdc
Normal file
36
.cursor/rules/tests-pest.mdc
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
description: Pest 4 feature/unit tests — file location, named routes, factories
|
||||
globs: tests/**/*.php
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Pest Tests
|
||||
|
||||
This project uses Pest for testing. Every change must be programmatically tested — write a new test or update an existing one, then run it.
|
||||
|
||||
## Creating tests
|
||||
|
||||
- Create tests via `php artisan make:test --pest {name}`. Do NOT include the test suite directory in the name. Use `php artisan make:test --pest SomeFeatureTest`, NOT `php artisan make:test --pest Feature/SomeFeatureTest`.
|
||||
- Run tests: `php artisan test --compact` or filter: `php artisan test --compact --filter=testName`.
|
||||
- Run the minimum number of tests needed for confidence. Prefer file or `--filter` over the whole suite.
|
||||
- Do NOT delete tests without approval.
|
||||
|
||||
## Named routes (IMPORTANT)
|
||||
|
||||
ALWAYS use named routes via the `route()` helper. NEVER hardcode URL strings.
|
||||
|
||||
```php
|
||||
// BAD
|
||||
$this->postJson('/posts');
|
||||
$this->postJson('/posts/ai/create');
|
||||
|
||||
// GOOD
|
||||
$this->postJson(route('app.posts.store'));
|
||||
$this->postJson(route('app.posts.ai.create.finalize', $creationId));
|
||||
```
|
||||
|
||||
## Factories
|
||||
|
||||
When creating models for tests, use factories. Check for custom states before manually setting up the model.
|
||||
|
||||
Faker: use `$this->faker->word()` or `fake()->randomDigit()` — follow existing convention in the file you're editing.
|
||||
65
.cursor/rules/vue-typescript.mdc
Normal file
65
.cursor/rules/vue-typescript.mdc
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
description: Vue 3 + TypeScript conventions — arrow functions, icons, dates, wayfinder, form validation, imports
|
||||
globs: **/*.{vue,ts}
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Vue / TypeScript
|
||||
|
||||
Vue components must have a single root element.
|
||||
|
||||
## Arrow functions
|
||||
|
||||
Always use arrow functions in Vue components and TypeScript files. Never use `function` declarations.
|
||||
|
||||
```ts
|
||||
// BAD
|
||||
function handleClick() {}
|
||||
|
||||
// GOOD
|
||||
const handleClick = () => {}
|
||||
```
|
||||
|
||||
## Icons (@tabler/icons-vue)
|
||||
|
||||
This project uses `@tabler/icons-vue` for all icons. NEVER use `lucide-vue-next`.
|
||||
|
||||
- All Tabler icons are prefixed with `Icon` — `IconCheck`, `IconChevronRight`, `IconMail`.
|
||||
- Import from `@tabler/icons-vue`: `import { IconCheck, IconX } from '@tabler/icons-vue'`.
|
||||
- Browse available icons at https://tabler.io/icons.
|
||||
|
||||
## Dates
|
||||
|
||||
- For date manipulation, always use `@/dayjs` (pre-configured with utc, timezone, relativeTime plugins).
|
||||
- For formatting (`formatDate`, `formatDateTime`, `formatTime`, `diffForHumans`), always use `@/date` — it centralizes formatting with proper timezone handling.
|
||||
- NEVER use raw `new Date()` for date calculations. Use dayjs.
|
||||
|
||||
## Routing (Wayfinder)
|
||||
|
||||
- This project uses Laravel Wayfinder for type-safe frontend routing.
|
||||
- ALWAYS use Wayfinder-generated route helpers in Vue pages (`register()`, `login()`, `dashboard()`). NEVER hardcode URL strings like `href="/register"`.
|
||||
- After creating or modifying PHP routes/controllers, run `php artisan wayfinder:generate` to regenerate the TypeScript route helpers.
|
||||
- Import routes from `@/routes/...` — e.g. `import { store } from '@/routes/login'`.
|
||||
- Import controller actions from `@/actions/...`.
|
||||
|
||||
## Form validation
|
||||
|
||||
NEVER use HTML5 validation attributes (`required`, `minlength`, `pattern`, etc.) on form inputs. Always rely solely on backend validation.
|
||||
|
||||
```vue
|
||||
<!-- BAD -->
|
||||
<input type="email" required minlength="3" />
|
||||
|
||||
<!-- GOOD -->
|
||||
<input type="email" />
|
||||
```
|
||||
|
||||
## Imports
|
||||
|
||||
Always import at the top of the file with `import` statements. Never use inline references.
|
||||
|
||||
```ts
|
||||
// GOOD
|
||||
import { ref } from 'vue'
|
||||
const count = ref(0)
|
||||
```
|
||||
Loading…
Reference in a new issue