- Write LESS code. Reuse existing code. No redundant code.
- Analyse codebase before adding new code. Check sibling files for conventions.
- Never run
npm build/devorartisan serve— user runs them. - Don't run formatters (pint/prettier) — user runs them later.
- Authoritative standards live in
.github/copilot-instructions.mdand.github/instructions/*.md— read those before writing code in an unfamiliar area. - Run
/review-prbefore declaring work done.
Laravel 13 (L10 structure), PHP 8.4, Inertia v2, React 19, Tailwind v4, PHPUnit 12
search-docs: Use FIRST for Laravel ecosystem docs (version-aware). Pass multiple broad queries.list-artisan-commands: Check before running artisan commands.tinker: Debug PHP/Eloquent.database-query: Read-only DB queries.database-schema: Inspect tables/columns before writing migrations or queries.get-absolute-url: For sharing project URLs.browser-logs: Recent browser errors/exceptions.
- Curly braces always. Constructor property promotion. Explicit return types on all methods.
- PHPDoc blocks for documentation. No inline
//comments inside function or method bodies (migrations excepted). - Use array shapes in PHPDoc where appropriate.
- Avoid
DB::, preferModel::query(). Eager load to prevent N+1. - Use
config()notenv()outside config files.
Vito has a specific architecture. Match these patterns exactly:
- Models extend
app/Models/AbstractModel.php, NOTIlluminate\Database\Eloquent\Model.AbstractModelincludesHasTimezoneTimestampsandjsonUpdate(). - Enums implement
app/Contracts/VitoEnum.php— requiresgetColor(): stringandgetText(): string. Cast enum properties to their enum class on the model. - Actions in
app/Actions/hold ALL business logic. Methods namedcreate()/update()/handle()/run()/install()etc. Validate inside the Action withValidator::make(); extract to a privatevalidate()when rules are complex. Compose Actions instead of building monoliths. - Controllers are thin: receive request → call Action → return response. Use
Spatie\RouteAttributes(#[Get],#[Post],#[Prefix],#[Middleware]) and ALWAYS name routes (#[Get('/', name: 'servers.index')]). ReturnInertia::render()for web,JsonResourcefor API. Call$this->authorize()for policy checks. - Policies in
app/Policies/use theHasRolePoliciestrait — provideshasReadAccess(),hasWriteAccess(),hasOwnerAccess(). No inline permission checks anywhere. - Jobs implement
ShouldQueuewith bothQueueableANDUniqueQueuetraits. Wrap work in$this->run($key, callable). Implementfailed(Exception $e)for status updates. Prefer queued jobs over long-running pollers. - API Resources in
app/Http/Resources/. Explicitly whitelist fields — neverparent::toArray(). ALWAYS call->getText()/->getColor()on enum properties — never return raw enum values. - Broadcasting uses
SocketEvent::dispatch()with aSocketEventDTO(projectId,typelike'server.updated',datatypically an API Resource). - Bootstrap context: shared catalogue data (provider lists, site types, GitHub App install state, public key text) is served from
App\Actions\Bootstrap\GetBootstrapvia/bootstrapand cached client-side viauseConfigs(). Add new catalogue values toGetBootstrap::configs(), NOT toHandleInertiaRequests::share(). Any Action that mutates DB/runtime state reflected inconfigs()must callGetBootstrap::forgetVersion()after the mutation — otherwise the cachedbootstrap_versionnever changes and clients see stale data forever. Keepresources/js/types/index.d.tsConfigsin sync. - Providers (Server/DNS/Storage/SourceControl): Interface + AbstractProvider pattern. Extend the abstract (e.g.
AbstractServerProvider), implementstatic id(): string. Register via plugin classes inapp/Plugins/. - Services (database/webserver/PHP/etc.): extend
AbstractService, implementServiceInterface. Implementstatic id()andstatic type(). SSH scripts live inresources/views/ssh/services/[service-name]/.
- Use
php artisan make:*with--no-interactionfor new files. - Don't use FormRequests. All validation lives in Actions via
Validator::make(). - Return 422 (validation exception) for invalid input — never 400.
- Wrap multi-step mutations in
DB::transaction(). Critical for delete-then-recreate flows (e.g. DNS record sync). - Null-check nullable relationships —
nullOnDelete()foreign keys can be null at runtime. - Let provider/service errors bubble up; don't silently swallow exceptions.
- Use
'encrypted:json'cast for credentials and secrets on models. - API keys are project-scoped — enforce the project boundary in queries and policies.
- Schema changes and data transforms only. Never dispatch jobs, run SSH, make HTTP calls, or broadcast events from migrations.
- Combine related schema changes into a single migration file per feature.
- Never run
migrate:freshor other destructive DB commands — use tests to verify migrations.
- Inertia pages in
resources/js/pages/. React components inresources/js/components/. Functional components + hooks. - Forms: use the
useFormhelper, follow existing patterns. Navigation:<Link>orrouter.visit()— never raw<a>for internal routes. - Dialogs: all modals/sheets use the centralized registry (
resources/js/components/dialogs/registry.ts) opened viauseDialog()—dialog.<key>.open(props), ordialog.confirm.open({...})for simple confirms. Never nest<Dialog>/<DialogTrigger>inside aDropdownMenuItem. Read the "Dialogs" section of.github/instructions/frontend.instructions.mdbefore adding or changing any dialog. - Tailwind v4:
@import "tailwindcss",@themefor config,gap-*over margins for sibling spacing. - Use Shadcn components and semantic tokens (
text-foreground,bg-background,text-muted-foreground). Avoid hard-coded colors and custom CSS. - React hooks: include ALL dependencies in
useEffect/useMemo/useCallbackarrays. Return cleanup for timers/subscriptions. Stale closures from missing deps are a recurring bug here. - Dynamic forms: backend ships
DynamicField/DynamicFormDTOs (types: text, password, password-with-toggle, textarea, select, checkbox, alert, component). Render based onfield.type. Don't hardcode provider-specific fields. Respect the field's value type — don't force everything to string. - Accessibility:
<button>for clickable elements, not<span onClick>. Add ARIA attributes when semantic HTML isn't enough. - TypeScript types: keep
resources/js/types/*.d.tsin sync with backend API Resources. Enum status fields arrive as{ status: string, status_color: string }. - Handle promise rejections from browser APIs (
navigator.clipboard.writeText, etc.) and show error feedback.
- PHPUnit only. Create with
php artisan make:test --phpunit. TestCaseprovides$this->user,$this->server,$this->sitepre-configured.- Test logic only, not framework. Use factories. RefreshDatabase trait.
- Add to existing test files when possible. Run minimal tests with
--filter. - Don't remove tests without approval.
- Feature tests for user flows, Unit tests for isolated logic.
- Use
SSH::fake()for SSH operations andHttp::fake()for HTTP — never make real connections. - Use
assertDatabaseHas()for DB assertions.
- Consider API endpoints for new features but ask user first.
- Use API Resources for consistent responses.
- Document endpoints in
public/api-docs/openapi. Keep schemas in sync with API Resources and backend enums. Don't document fields the API intentionally omits.
- Every web/API endpoint must be protected by appropriate middleware AND a Policy.
- Never log secrets, tokens, or credentials at any log level. Redact or hash if debugging.
- Never expose internal filesystem paths (CSR/private key paths, etc.) in API responses or OpenAPI.
- When editing credentials, merge updates server-side. Never round-trip existing secrets through the frontend.
- No test-only routes or UI actions in production. Env-gate or remove before merging.
- All SSH goes through
app/Helpers/SSH.phpand theSSHfacade. Noexec(),shell_exec(),system(),passthru(), orSymfony\Component\Process\Process. - SSH scripts for remote servers are Blade templates in
resources/views/ssh/. - Validate and sanitize any user input that lands in an SSH command or template.
- When running as a different user,
cd/sudomust run as the target user, not the login user.
/review-pr [target-branch]runs the three project reviewer agents (php-laravel-reviewer,frontend-reviewer,security-reviewer) against the current branch's diff and returns prioritised findings.- Auto-detects PR target via
ghif a PR is open; otherwise prompts for the target branch.
- Use
ghCLI for issues/PRs. - Don't change dependencies or create new base folders without approval.