=== foundation rules === # Laravel Boost Guidelines ## Foundational Context This application is a Laravel application running on PHP 8.5. Always use the APIs that match the installed major version of each package — do not assume a version. Before relying on a package's API, confirm its installed version: - PHP packages: run `composer show --direct` to list direct dependencies with versions, or `composer show ` for a single package. - JS packages: check `package.json` for the installed versions. ## Skills Activation This project has domain-specific skills available in `**/skills/**`. You MUST activate the relevant skill whenever you work in that domain—don't wait until you're stuck. ## 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 a frontend change doesn't show in the UI or you get a "Unable to locate file in Vite manifest" error, run `npm run build` or ask the user to run `npm run dev` or `composer run dev`. ## Documentation Files - You must only create documentation files if explicitly requested by the user. === boost rules === # Laravel Boost ## Tools - Laravel Boost is an MCP server with tools designed specifically for this application. Prefer Boost tools over manual alternatives like shell commands or file reads. - Use `database-query` to run read-only queries against the database instead of writing raw SQL in tinker. - Use `database-schema` to inspect table structure before writing migrations or models. - Use `get-absolute-url` to resolve the correct scheme, domain, and port for project URLs. Always use this before sharing a URL with the user. - Use `browser-logs` to read browser logs, errors, and exceptions. Only recent logs are useful, ignore old entries. ## Searching Documentation (IMPORTANT) - Use `search-docs` before changes that depend on Laravel ecosystem APIs, behavior, configuration, or version-specific syntax. Skip it for copy-only edits and other changes where package documentation is irrelevant. Reuse sufficient results already in context instead of searching again. - 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']`. Expect the most relevant results first. - Do not add package names to queries because package info is already shared. Use `test resource table`, not `filament 4 test resource table`. ### Search Syntax 1. Use words for auto-stemmed AND logic: `rate limit` matches both "rate" AND "limit". 2. Use `"quoted phrases"` for exact position matching: `"infinite scroll"` requires adjacent words in order. 3. Combine words and phrases for mixed queries: `middleware "rate limit"`. 4. Use multiple queries for OR logic: `queries=["authentication", "middleware"]`. ## Project Rules - This project contains committed, area-grouped rules in `.ai/rules` when that directory exists, including path-scoped framework guidelines under `.ai/rules/boost`. Before you enter plan mode or create/edit any file, you MUST first: open @.ai/rules/index.md (it maps file globs to rule files), read every rule file whose globs cover the path(s) in scope, and run `grep -rin 'keyword' .ai/rules` to catch what a path match alone misses. Do not write code until you have read and are following every matching rule. If `.ai/rules` does not exist, continue without it. ## Artisan - Run Artisan commands directly via the command line (e.g., `php artisan route:list`). Use `php artisan list` to discover available commands and `php artisan [command] --help` to check parameters. - Inspect routes with `php artisan route:list`. Filter with: `--method=GET`, `--name=users`, `--path=api`, `--except-vendor`, `--only-vendor`. - Read configuration values using dot notation: `php artisan config:show app.name`, `php artisan config:show database.default`. Or read config files directly from the `config/` directory. ## Tinker - Execute PHP in app context for debugging and testing code. Do not create models without user approval, prefer tests with factories instead. Prefer existing Artisan commands over custom tinker code. - Always use single quotes to prevent shell expansion: `php artisan tinker --execute 'Your::code();'` - Double quotes for PHP strings inside: `php artisan tinker --execute 'User::where("active", true)->count();'` === php rules === # PHP - Always use curly braces for control structures, even for single-line bodies. - Use PHP 8 constructor property promotion: `public function __construct(public GitHub $github) { }`. Do not leave empty zero-parameter `__construct()` methods unless the constructor is private. - Use explicit return type declarations and type hints for all method parameters: `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. === deployments rules === # Deployment - Laravel can be deployed using [Laravel Cloud](https://cloud.laravel.com/), which is the fastest way to deploy and scale production Laravel applications. === tests rules === # Test Enforcement - Add or update tests for behavior and logic changes when a test provides meaningful regression coverage. - Pure copy, styling, and layout-only changes do not require new or updated tests. - When test coverage applies, run the affected tests and ensure they pass. - Test the changed behavior and its important failure modes, but do not add tests beyond them. - Read the `testing-best-practices` skill before writing tests. === 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. ### 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. ## URL Generation - When generating links to other pages, prefer named routes and the `route()` function. ## 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. === laravel-octane/core rules === # Laravel Octane This application uses Laravel Octane, a long-running PHP server. The application bootstraps once and handles many requests within the same process. - Never store request-specific state in singletons or static properties, because it can leak across requests. - Use `config('octane.server')` to detect the active driver (`swoole`, `roadrunner`, or `frankenphp`). - Prefer scoped bindings (`$this->app->scoped()`) over singletons for per-request services. When working on Octane-specific features (concurrency, shared tables, memory, driver configuration, testing), invoke `octane-development` for detailed rules. === livewire/core rules === # Livewire - Livewire allows you to build dynamic, reactive interfaces in PHP without writing JavaScript. - You can use Alpine.js for client-side interactions instead of JavaScript frameworks. - Keep state server-side so the UI reflects it. Validate and authorize in actions as you would in HTTP requests. === 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. Create tests with `php artisan make:test --pest {name}`. - Do not include the test suite directory in `{name}`. Use `SomeFeatureTest`, not `Feature/SomeFeatureTest`. - Read the `testing-best-practices` skill for guidance on coverage, naming, structure, dependency isolation, and review. - Do not delete tests or test files without approval. They are part of the application. ## Running Tests - Run the narrowest set of tests that covers the change. Pass a file path or `--filter=testName` to `php artisan test --compact`. - Rerun a test after each change to it. - Run `vendor/bin/pest` to call the test runner directly. It accepts the same file path and `--filter=testName` arguments. - After the feature tests pass, ask the user to run the complete suite with `php artisan test --compact`. === nonameweb/livewire-material/core rules === ## Livewire Material This application uses `nonameweb/livewire-material`: Material 3 Expressive components for Laravel and Livewire, in plain CSS. No utility classes — Tailwind, daisyUI or any other — belong here: a class your CSS does not declare does nothing, and `DesignGuard` fails it. - Components are anonymous Blade components, unprefixed unless `config/livewire-material.php` sets a `prefix`. Before writing or changing a view that uses them, activate the `livewire-material-development` skill for the props, slots and traps of each component. - The CSS entry imports `foundation.css` first, then the stylesheet of each component the views render (or `all.css` for all of them). A component whose stylesheet is not imported renders unstyled; `DesignGuard::missingStylesheets()` names each missing `@import`. - Every layout includes `` in `` before `@vite`. The colour scheme is generated with `php artisan material:scheme` — never edit `resources/css/material-scheme.css` by hand. With colour profiles (`livewire-material.profiles`), run it without a seed after changing them; the active profile comes from `Scheme::resolveProfileUsing()`. - While the application runs locally, every token and component renders in the application's own scheme at `/material` (the showcase). - HTTP error pages and the Markdown mail theme come from the package. Change error wording by publishing `--tag=livewire-material-errors`; select the mail theme with `MAIL_MARKDOWN_THEME=livewire-material::mail.theme`. === nonameweb/livewire-material/material-3 rules === ## Material 3 Every view in this application is Material 3 Expressive (m3.material.io), through `nonameweb/livewire-material`. These rules decide what to write; the `material-3-design` skill carries the tables, the numbers and Google's source pages behind each one — activate it before designing a screen. The library is plain CSS on M3's tokens. No utility classes — Tailwind, daisyUI or any other — belong here: a class your CSS does not declare does nothing, and `DesignGuard` fails it. A view is written three ways: - Components and their props: ``, and the layout components ``, ``, ``, ``, `` and ``, whose `gap` and `padding` take a spacing token (`space200`) and whose `hide-below`, `hide-from` and `stack-below` take a window size class. - A fixed set of classes for text and interaction on plain elements: `md-type-*`, `md-ink-*`, `md-text-*`, `md-truncate`, `md-tabular`, `md-visually-hidden`, `md-state-layer`, `md-focus-ring`, `md-touch-target` and `md-link`. - The application's own CSS, named by the application, whose values are `--md-sys-*` custom properties. ### Colour - A colour is always a role: `md-ink-variant` on text, `var(--md-sys-color-outline-variant)` in the application's CSS, `color="error"` on a component. Never a hex, a palette tone or an opacity. - Pair a role only with its `on-` partner: a `primary` fill takes `on-primary` text, a `secondary-container` fill takes `on-secondary-container`. That pair is the one whose contrast is guaranteed at every contrast level; mixing pairs (`primary-container` under `on-surface`) is not. - `primary` is the one key action on a screen (a filled button; the FAB in `primary-container`). `secondary-container` is the quiet fill (tonal buttons, selected navigation, selected chips). `tertiary` is a contrasting accent, used rarely. `error`, `success`, `warning`, `info` mean state and nothing else: the `-container` for a tinted panel, the role itself for its text and icon. - Ink is `on-surface` (`md-ink`); lower emphasis is `on-surface-variant` (`md-ink-variant`); decoration is `outline` (`md-ink-quiet`). Never dim ink with an opacity: 38% means disabled. - `outline` is a boundary that must be read (a text field, the edge of a target). `outline-variant` is a divider or a card edge (``, ``). Never `outline` on a divider. - Fixed and dim roles (`primary-fixed`, `surface-dim`, …) are for a colour that must not change with the theme; if unsure, don't. Inverse roles only on an inverse surface (the snackbar). - A link in running text is underlined (`md-link`, with `md-ink-primary`); colour alone signals nothing. - Contrast: 4.5:1 for text, 3:1 for large text, icons and grouped controls; disabled is exempt. Three contrast levels exist (``: standard, medium, high) and every role changes with them — which is why only roles are allowed. ### Surfaces and elevation - The page is `surface`. Panels separate by tone first: `surface-container-lowest` … `surface-container-highest` is a hierarchy of emphasis, not of height (``). Navigation chrome is `surface-container`; a dialog, a menu, the search bar are `surface-container-high`; a modal sheet is `surface-container-low`; a filled card is `surface-container-highest`. A region keeps its role at every width. - Shadows (`var(--md-sys-elevation-1)` … `-5`) are for what floats or lifts: 1 for elevated cards, buttons and modal sheets; 2 for menus, the navigation bar, a scrolled app bar; 3 for the FAB, dialogs, pickers and search; one level more on hover; nothing rests above 3. Fewer shadows carry more meaning. - A scrim is `scrim` at 32%: `color-mix(in srgb, var(--md-sys-color-scrim) 32%, transparent)`. ### Shape - Corners come from the scale `var(--md-sys-shape-corner-{none|xs|sm|md|lg|lg-increased|xl|xl-increased|xxl|full})`, or ``; never a length of your own. - By family: `full` buttons, icon buttons, chips' avatars, badges, switches, sliders, the search bar, navigation indicators; `xs` text fields, menus, snackbars, plain tooltips; `sm` chips; `md` cards, rich tooltips; `lg` the FAB and a side sheet's inner corners; `xl` dialogs, bottom sheets, the search view, pickers, carousel items; `xxl` large hero containers. - Nested shapes: inner radius = outer radius − padding; never the same radius inside and out. - A press squares a round shape (the components do it; nothing morphs on hover). The 35 ``s are decoration, never meaning, used sparingly. ### Type - Every text element carries one `md-type-*` class: `display` for hero figures and short marketing lines; `headline` for page and section titles; `title` for card, dialog and list-section titles; `body` for paragraphs (`md-type-body-lg` for reading); `label` inside components (buttons, chips, tabs, captions). In the application's CSS a style is `font: var(--md-sys-typescale-body-md)` with its `-tracking`; never a size, weight, line height or letter spacing of your own. - `md-type-emphasized-*` is opt-in: a selected item, a primary action, a headline, a badge — not decoration. - 40–60 characters per line; `md-tabular` on figures that change; text must scale to 200% without loss (containers grow, rows wrap, no fixed heights on text, no ellipsis without a way to read the rest). ### Motion - Position, size and shape move on the spatial springs (they overshoot): `transition: transform var(--md-sys-motion-spatial-default-duration) var(--md-sys-motion-spatial-default)` — `fast` for small elements, `slow` for large ones. Colour and opacity move on the effects springs (`--md-sys-motion-effects-*`), which never overshoot. Always pair an easing with its duration. - Entering decelerates, a permanent exit accelerates, a temporary exit (a sheet, a drawer) takes the emphasized curve; exits are shorter than entrances. - Everything that moves goes through these tokens, so reduced motion makes it instant; a literal duration is a bug. ### States and targets - Interactive elements carry `md-state-layer md-focus-ring`: hover 8%, focus 10%, pressed 10%, dragged 16% (`data-md-dragged`) of the content colour. Disabled is content at 38% and a container at 12% of `on-surface` (`--md-sys-state-disabled-content-opacity`, `--md-sys-state-disabled-container-opacity`, through `color-mix()`), with no state layer. Every state shows two indicators: colour plus a shape, an outline, an icon or a word. - Every target is at least 48×48px with 8px between targets (`md-touch-target` on anything drawn smaller); a denser layout is an opt-in prop, never a default. - Keyboard: Tab between components, arrows within one, Enter and Space activate, Escape dismisses; a dialog takes focus and gives it back to what opened it. ### Layout and breakpoints - Widths are M3's window size classes and only those: compact below 600px (the default), medium 600, expanded 840, large 1200, extra-large 1600. A layout component takes them as props (``, ``, ``); the application's CSS writes `@media (width >= 840px)`; a script asks `from()` and `upTo()` from `resources/js/breakpoints.js`. - What changes per class: compact — navigation bar, one pane, full-screen dialogs, a bottom sheet for choices; medium — collapsed rail, one pane; expanded — rail (collapsible), two panes, menus and basic dialogs; large and extra-large — the rail expanded, two panes, a third only at extra-large as a side sheet. `` does this; content lives in panes (``, `` for a list's second pane), never beside the rail by hand. - Margins are 16px below medium and 24px from it (`` draws them); spacing sits on the 4px grid as `space25` … `space900`, as padding and gaps on the parent, with margins only between layout regions. A fixed pane is 360px (expanded) or 412px (large); a side sheet at most 400px. - Write logical properties (`padding-inline-start`, `inset-inline-end`, `md-text-start`); directional icons mirror in RTL; charts and media controls stay LTR. Keep controls inside the safe area (`--material-safe-*`). ### Accessibility - Native elements first (`