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:
Paulo Castellano 2026-05-19 19:26:49 -03:00
parent 5f40b7bd6a
commit 4c9243ccb9
9 changed files with 446 additions and 0 deletions

View 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>
```

View 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.

View 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.

View 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.

View 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.

View 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.

View 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>
```

View 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.

View 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)
```