# Clusev — STRICT RULES
> **Non-negotiable.** Generated from `handoff.md` §4. These are the user's explicit,
> mandatory rules. If any instruction (mine, yours, or a future agent's) conflicts with a
> rule here, **STOP and ask** — do not "work around" it. Keep this file open while coding.
>
> *Stack note (2026-06-11): built on **Laravel 13** (handoff said 12; updated by user decision). The rules below are version-agnostic.*
---
## Quick gate — run through this before every commit
- [ ] **R1/R2** — Page is a **full-page, class-based Livewire component** mapped directly in `routes/web.php`.
- [ ] **R2** — Livewire **class** in `app/Livewire/…` **and** a matching **separate Blade view** in `resources/views/livewire/…`. **No Volt.**
- [ ] **R3** — No hard-coded hex/rgb in markup — only `@theme` token utilities (`bg-surface`, `text-accent`, …).
- [ ] **R4** — No inline `style="…"` **except** a progress bar's `width`.
- [ ] **R5** — Destructive/confirm action runs through a **wire-elements/modal**, never Alpine `confirm()`.
- [ ] **R6** — Files placed exactly per the §5 folder map.
- [ ] **R7** — Verified at **375 / 768 / 1280 px**; touch targets ≥ 44px.
- [ ] **R8** — Every PHP/Composer/Node/artisan command ran **inside the container**.
- [ ] **R9** — UI copy is **German**, **no emoji**; status via color/dots/pills.
- [ ] **R10** — Reused existing `@theme` tokens + Blade components (no ad-hoc colors/widgets).
- [ ] **R11** — Records exposed in URLs are addressed by **UUID**, never the integer PK.
- [ ] **R12** — every touched page loads in a **browser** at **HTTP 200** with **zero console errors** (the **loaded** state of `wire:init` pages, not just the skeleton).
- [ ] **Secrets** — `git status` shows **no** `.env*`, token, key, or home-dir dotfile staged.
---
## R1 — Pages are full-page Livewire components, mapped directly as routes
**Why.** One page = one component = one route. No controller indirection, no orphan Blade views.
Keeps routing, state, and view co-located and predictable.
✅ **Correct**
```php
// routes/web.php
use App\Livewire\Dashboard;
use App\Livewire\Servers\Show;
Route::get('/', Dashboard::class)->name('dashboard');
Route::get('/servers/{server}', Show::class)->name('servers.show');
```
❌ **Forbidden**
```php
// Controller returning a Blade view for a PAGE
Route::get('/', [DashboardController::class, 'index']);
class DashboardController {
public function index() {
return view('dashboard'); // ❌ pages do not belong to controllers
}
}
```
---
## R2 — Class-based Livewire only. Volt is FORBIDDEN.
**Why.** Class + separate view is explicit, testable, and conventional. Volt single-file
components hide logic in the view and are banned for this project.
✅ **Correct** — `php artisan make:livewire Servers/Show` (creates class **and** view)
```php
// app/Livewire/Servers/Show.php
namespace App\Livewire\Servers;
use App\Models\Server;
use Livewire\Component;
class Show extends Component
{
public Server $server;
public function render()
{
return view('livewire.servers.show'); // ← separate Blade file
}
}
```
```blade
{{-- resources/views/livewire/servers/show.blade.php --}}
{{ $server->hostname }}
```
❌ **Forbidden**
```blade
{{-- Volt single-file component — NEVER. Do not run `php artisan make:volt`. --}}
0]);
?>
{{ $count }}
```
---
## R3 — All colors / design tokens live in `resources/css/app.css` under `@theme`
**Why.** Single source of truth for the visual system (Tailwind v4 CSS-first, no
`tailwind.config.js`). Markup references **tokens**, never raw colors — so a token change
restyles the whole app.
✅ **Correct**
```css
/* resources/css/app.css */
@import "tailwindcss";
@theme {
--color-surface: #0F1318;
--color-accent: #FF6B2C;
--color-online: #35D07F;
--color-line: rgba(255,255,255,.10);
}
```
```blade
CPUonline
soft accent tint
{{-- opacity modifier, not a separate token --}}
```
❌ **Forbidden**
```blade
{{-- ❌ raw hex in markup --}}
```
---
## R4 — Inline styles are FORBIDDEN (one exception)
**Why.** Inline styles bypass the token system and Tailwind. The **only** legitimate dynamic
value that can't be a utility class is a progress bar's percentage width.
✅ **Correct** — the ONE allowed inline style
```blade
```
❌ **Forbidden**
```blade
{{-- ❌ use utilities: mt-3 flex text-accent --}}
```
---
## R5 — Destructive / confirm actions use a Modal, never an Alpine dialog
**Why.** Destructive actions (delete server, remove SSH key, stop a service) must be
deliberate, styled, and auditable. Use **wire-elements/modal** components — never native
`confirm()` or an `x-data` popup.
✅ **Correct**
```blade
{{-- trigger: open the modal component (exact payload per installed wire-elements/modal version) --}}
```
```php
// app/Livewire/Modals/ConfirmDelete.php (a real Livewire modal class)
namespace App\Livewire\Modals;
use App\Models\Server;
use LivewireUI\Modal\ModalComponent; // wire-elements/modal base class
class ConfirmDelete extends ModalComponent
{
public Server $server;
public function delete(): void
{
$this->server->delete(); // audit-logged in the domain layer
$this->closeModalWithEvents([/* refresh listeners */]);
}
public function render() { return view('livewire.modals.confirm-delete'); }
}
```
❌ **Forbidden**
```blade
{{-- ❌ no native confirm(), no Alpine confirm popups for destructive actions --}}
```
---
## R6 — Clean, conventional folder structure
**Why.** Predictable structure = fast ramp-up, no duplication, no "where does this go?".
Follow the §5 map in `CLAUDE.md` exactly.
✅ **Correct** — component, view, model, SSH wrapper each in their canonical place
```
app/Livewire/Servers/Show.php
resources/views/livewire/servers/show.blade.php
app/Models/Server.php
app/Support/Ssh/SshClient.php
```
❌ **Forbidden**
```
app/Http/Controllers/ServerController.php {{-- ❌ pages aren't controllers (R1) --}}
app/Livewire/show.blade.php {{-- ❌ view in the class namespace --}}
app/ServerSshHelperStuff.php {{-- ❌ unstructured dumping ground --}}
```
---
## R7 — Fully responsive: mobile + tablet + desktop
**Why.** Operators use phones and tablets in the field. Every screen must adapt — not just shrink.
✅ **Correct**
```blade
{{-- KPI grid reflows 1 → 2 → 4; sidebar is drawer on small, fixed on large --}}