diff --git a/CLAUDE.md b/CLAUDE.md
index aec188c..5ecba66 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -227,7 +227,9 @@ docker compose -f docker-compose.prod.yml exec app php artisan migrate --force
- **Work on a feature branch, not `main`.** Commit in sensible steps.
- **Secret hygiene:** `.env*` (incl. `.env.gitea`) + keys are gitignored; never commit/echo the
Gitea token (read it only at push time). See `rules.md` → Appendix.
-- **Verify before "done":** container builds, page renders, no console errors. Test the 3 breakpoints.
+- **Verify before "done" (R12):** load **every touched page in a real browser** (headless ok) →
+ **HTTP 200** (no 500) + **zero console/network errors**; for lazy `wire:init` pages check the
+ **loaded** state, not the skeleton. A green `Livewire::test` is **not** enough. Test the 3 breakpoints.
---
@@ -243,5 +245,7 @@ docker compose -f docker-compose.prod.yml exec app php artisan migrate --force
7. Run all tooling **inside the container** (R8).
8. UI copy **German, no emoji**; status via color/dots/pills (R9).
9. **Reuse** the token set + Blade component kit (R10).
-10. **Verify** (build, render, no console errors, 3 breakpoints), then `git status` for stray
+10. **Verify in a browser (R12)**: every touched route loads at **HTTP 200** with **zero console
+ errors** (loaded state of lazy pages, not the skeleton) — a green `Livewire::test` is not enough;
+ build + 3 breakpoints; then `git status` for stray
secrets, commit on the feature branch.
diff --git a/resources/views/livewire/services/index.blade.php b/resources/views/livewire/services/index.blade.php
index 7cf3a62..80d4389 100644
--- a/resources/views/livewire/services/index.blade.php
+++ b/resources/views/livewire/services/index.blade.php
@@ -70,9 +70,9 @@
{{-- R5: wire to wire-elements/modal --}}
- Starten
- Stopp
- Neustart
+ Starten
+ Stopp
+ Neustart
diff --git a/rules.md b/rules.md
index 273eddc..48d0c7b 100644
--- a/rules.md
+++ b/rules.md
@@ -21,6 +21,7 @@
- [ ] **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.
---
@@ -336,6 +337,40 @@ route('servers.show', $server->id); // exposes the auto-increment id
---
+## R12 — Browser-verified: HTTP 200, zero console errors
+
+**Why.** A passing `Livewire::test(...)->assertOk()` only renders ONE state in PHP — it does not
+catch client-side JS errors, and for a `wire:init`/lazy page it exercises the **skeleton**, not the
+loaded view. Render-time Blade/PHP errors (e.g. `@disabled` on a component → "unexpected endif", a
+500) only surface on the real request. A green unit test is **not** "done".
+
+**Rule.** Before declaring any UI change done, load **every affected page in a real browser**
+(headless is fine) and confirm, per page: **HTTP 200** (no 500), **zero console errors**, **zero
+failed requests**. For lazy pages, verify the **loaded** state (after `wire:init`), not the skeleton.
+Never use `@disabled`/`@checked`/`@required` on a Blade **component** — use the bound `:disabled="…"`
+attribute (the component attribute bag drops a `false` value correctly).
+
+✅ **Correct** — headless probe over the routes (captures console + HTTP status):
+```bash
+# mount the script into the image's home dir or require('puppeteer') fails
+docker run --rm --network host -v /tmp/probe.js:/home/pptruser/probe.js -w /home/pptruser \
+ ghcr.io/puppeteer/puppeteer:latest node probe.js
+```
+```js
+page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });
+const r = await page.goto(url, { waitUntil: 'networkidle2' }); // expect r.status() === 200
+await new Promise(s => setTimeout(s, 4000)); // let wire:init load, then assert errors == 0
+```
+
+❌ **Forbidden**
+```text
+"Livewire::test rendered OK" → shipping without ever loading the page in a browser.
+Testing only the skeleton state of a wire:init page and assuming the loaded state works.
+@disabled($cond) on → injects if/endif into the component tag → 500.
+```
+
+---
+
## Appendix — Secret hygiene
The project lives in `/home/nexxo/clusev` (its own git repo). The Gitea push token lives in