diff --git a/cypress/REPORT-STAGING-UPDATE.md b/cypress/REPORT-STAGING-UPDATE.md
new file mode 100644
index 0000000..5c7c62d
--- /dev/null
+++ b/cypress/REPORT-STAGING-UPDATE.md
@@ -0,0 +1,101 @@
+# Abschlussbericht — Cypress-Suite-Update auf staging-Stand (2026-07)
+
+> Aktualisierung der bestehenden Cypress-Integrationstest-Suite an den neuen `origin/staging`-Stand (~7.800 geänderte `src/`-Zeilen seit der ursprünglichen Test-Baseline `d1af1a7`). Reparatur veralteter Tests + neue Feature-Abdeckung. Arbeitsstand: Worktree-Branch `integrationstests-staging-update`.
+
+## Ergebnis
+
+| Check | Ergebnis |
+| --- | --- |
+| **Cypress-Suite** | **46/46 Tests grün** über **30 Specs**, 0 failing (~61s) |
+| **TypeScript** (`tsc --noEmit -p cypress/tsconfig.json`) | 0 Fehler |
+| **ESLint** | 0 Fehler, 40 Warnings (alle pre-existing) |
+| **Build** (`vite build`) | success |
+
+## Zahlen
+
+| Metrik | Wert |
+| --- | --- |
+| Commits in dieser Session | **21** |
+| Reparierte bestehende Tests | **5** |
+| Reaktivierte Tests (vorher `skip`) | **1** (Admin-Guard) |
+| Neue Feature-Tests | **11** (2× P0, 7× P1, 2× P2) |
+| Neue Fixtures | ~12 (Student, Lecturer, Admin-Deployments, Redeploy-Params, Course-Filter, …) |
+| Produktivcode-Änderungen | **0** |
+| Neue `data-testid` | **0** (alle Tests über role/text/id/aria-Selektoren) |
+
+## Kontext des Updates
+
+`origin/staging` hatte sich seit der ersten Suite massiv weiterentwickelt. Der aktuelle Checkout (`feature/192`) und die alte `integrationstests`-Arbeit waren bereits nach `staging` gemerged. Neu hinzugekommen: Student-Self-Service, Lecturer-Verwaltung (Admin), Admin-Rework (Split von `/admin`), Redeploy/VM-Config-Override, Kurs-Filter, 36-Monate-Laufzeit, Expiry-Farbstufen, Owner-Only-Gating, `ProtectedRoute`, `MobileTopBar`, `CredentialInstanceCard`.
+
+## Teil A — Reparaturen (6)
+
+| Test | Ursache | Fix | Commit |
+| --- | --- | --- | --- |
+| `auth-admin-bypasses-setup-gate` | `/admin` gesplittet | Ziel → `/admin/projects`, Heading „Projektübersicht" | e762bc2 |
+| `admin-approve-template-version` | Queue nach `/admin/templates` verschoben | Route angepasst | a3d1705 |
+| `detail-delete-confirmation-flow` | Owner-Only-Gate (fix/126) | `owner_id` in Fixture | a14815f |
+| `auth-non-admin-cannot-access-admin-route` | war geskippt; Befund via `ProtectedRoute` behoben | neu geschrieben (Redirect-Assertion) + reaktiviert | 265a90d |
+| `detail-issue-207-ungrouped-members` (fremd) | Card-Titel off-screen | `scrollIntoView()` | e04c570 |
+| `visual-capture` (fremd) | Mobile: `cy.contains` traf versteckten Sidebar-Link; falscher Reference-Pfad | `cy.contains("h1", …)` + Pfad-Fix | 6d58f9f, 20477c3 |
+
+## Teil B — Neue Feature-Tests (11)
+
+### Student-Self-Service
+- `student-dashboard-lists-deployments` **[P0]** (1ca72d3) — Routing `/` → `/student/dashboard` + Deployment-Karten
+- `student-role-routing-guard` **[P0]** (134f4e1) — Student auf `/dashboard`/`/appstore` → Redirect; keine Lecturer-Calls
+- `student-deployment-details-credentials` **[P1]** (60fec05) — VMs + Credentials (CredentialInstanceCard mode=student)
+
+### Admin-Bereich
+- `lecturer-management-lists` **[P1]** (f35a9ed) — Lecturer-Tabelle + Detail-Dialog
+- `lecturer-delete-cascade-poll` **[P1]** (13fb357) — Type-to-confirm + DELETE 202 + Poll 200→404
+- `admin-project-overview-renders` **[P1]** (b231c48) — Stat-Cards + View-Selector (Dozent/Kurs/Datum)
+- `admin-reject-template-with-reason` **[P1]** (93bc5cb) — Reject-Flow mit Begründung im Payload
+
+### Deployment / Courses
+- `redeploy-deployment-config-override` **[P1]** (ce5e739) — RedeployDialog, nur geänderte Params im Override
+- `course-filters-toggle-chip` **[P1]** (317482c) — client-seitige Chip-Filterung
+- `course-filters-admin-crud` **[P1]** (aac5a7d) — Admin: Filter anlegen (POST) + löschen (DELETE)
+
+### Wizard / Dashboard
+- `wizard-36-months-runtime` **[P2]** (26fc782) — „3 Jahre"-Laufzeit → `runtime_months: 36`
+- `dashboard-expiry-color-tiers` **[P2]** (fde35a6) — warning/critical/expired-Indikatoren (relative Daten)
+
+## Infrastruktur-Anpassungen
+
+- **`Role`-Union** um `"student"` erweitert (commands.ts + index.d.ts, Commit 0115898) — nutzt bestehende `keycloak/student.json`.
+- **Delta-Matrix** `cypress/TEST-MATRIX-DELTA.md` (9f681f5) dokumentiert Reparaturen + neue Tests.
+- **`cy.mockApi`** deckte bereits `template-versions/queue` + `openstack/flavors` ab (aus der ersten Session).
+
+## Bestätigte Sicherheits-Verbesserung
+
+Der ursprünglich in `SECURITY-FINDINGS.md` dokumentierte Privilege-Escalation-Befund (`/admin` ohne Frontend-Rollen-Gate) ist auf `staging` **behoben**: `ProtectedRoute requireAdmin` leitet Nicht-Admins auf `/dashboard` um. Der zuvor geskippte Test ist reaktiviert und sichert den Guard; das Finding wurde auf **RESOLVED** aktualisiert.
+
+## Umgebungs-Hinweis (nicht committed)
+
+Auf diesem Rechner (Apple M2, aber **x64-node unter Rosetta**) kollidiert Cypress' gebündeltes arm64-esbuild mit dem x64-node beim Kompilieren der TS-Config. Lokaler Workaround: `ESBUILD_BINARY_PATH` auf ein passendes x64-esbuild-0.28.0-Binary setzen — **nur für Cypress**, nicht für Vite (das braucht 0.25.12). Ein lokales Runner-Skript `/tmp/local-cypress-run.sh` kapselt das. **Nichts davon wurde committed** — auf einer arch-konsistenten Maschine / in CI ist der Workaround unnötig und würde dort sogar stören. `node_modules` im Worktree ist ein Symlink auf den Haupt-Checkout.
+
+## Verbliebene Risiken / Nicht abgedeckt
+
+- **`TemplateIconUpload`** (Multipart-Icon-Upload im AppStore-Owner-Dialog) — nicht getestet (Datei-Upload + Multipart-Intercept aufwändig).
+- **SSH-Key-Download** im Student-Detail (Blob + Content-Disposition) — nur Anzeige getestet, nicht der Download.
+- **`NoRolePage`** — laut Analyse ist `/no-role` nicht als Route registriert (nur ProtectedRoute-Redirect-Ziel), landet über Catch-all bei `/dashboard`. Potenzielle Lücke, nicht durch Test fixiert.
+- **Rename-Flow** der Course-Filter (PATCH) — nur Create/Delete getestet.
+- **Fehlerpfade** vieler neuer Features (403/404/409) — überwiegend Happy-Path abgedeckt, analog zur bestehenden P0/P1-Tiefe.
+- **Redeploy pro VM** (Instance-Redeploy) — nur der Deployment-weite Redeploy getestet.
+
+## Mögliche zukünftige Testfälle
+
+1. `student-credentials-error-403/404` — Fehlerpfade der Credentials
+2. `redeploy-instance-per-vm` — VM-einzelner Redeploy
+3. `redeploy-only-owner-or-running` — Permission/Status-Gate des Redeploy-Buttons
+4. `course-filters-rename` — PATCH-Flow
+5. `course-filters-lecturer-readonly` — Lecturer sieht keine CRUD-Controls
+6. `template-icon-upload` — Multipart-Upload + Preview
+7. `lecturer-delete-timeout` — Poll läuft 30s ohne 404 → Timeout-State
+8. `admin-project-overview-drilldown` — Klick in Dozent/Kurs-Gruppe → Detailtabelle
+9. `mobile-topbar-drawer-nav` — Hamburger-Drawer-Navigation (Viewport-Test)
+10. `wizard-extend-runtime` — Laufzeit-Verlängerung (PATCH /extend) auf der Detailseite
+
+## Nächste Schritte
+
+Branch `integrationstests-staging-update` ist **21 Commits vor `origin/staging`**. Nach Freigabe: `git push` + PR nach `staging`. Für CI müsste `npm run test:e2e` (via `start-server-and-test`) noch als Pipeline-Schritt ergänzt werden, damit die Suite auf jedem PR läuft — auf arch-konsistenten Runnern ohne den esbuild-Workaround.
diff --git a/cypress/SECURITY-FINDINGS.md b/cypress/SECURITY-FINDINGS.md
index ed06387..76a4703 100644
--- a/cypress/SECURITY-FINDINGS.md
+++ b/cypress/SECURITY-FINDINGS.md
@@ -8,13 +8,17 @@
**Severity**: High (privilege escalation, defence-in-depth)
**Discovered while writing**: `cypress/e2e/auth/auth-non-admin-cannot-access-admin-route.cy.ts`
-**State**: Test is `describe.skip(...)` until the fix lands.
+**State**: ✅ **RESOLVED on `staging`** (admin-area rework). The test is now un-skipped and active — it passes against the fixed code and guards against regression.
-### Finding
+### Resolution
-`AdminMonitoring.tsx` has no role gate. `App.tsx` line 159 wires `` at `/admin` for any authenticated user. The Sidebar nav link IS gated (`Sidebar.tsx` line 121 checks `isAdmin`), but that is only a UI affordance, not a security boundary.
+The admin-area rework replaced the single ungated `/admin` route with three routes — `/admin/projects`, `/admin/templates`, `/admin/lecturers` — each wrapped in `` (`src/components/ProtectedRoute.tsx`). For a non-admin, `requireAdmin && !isAdmin` returns ``, so `AdminTemplateApprovals` / `AdminProjectOverview` never mount and their data effects never run. `AdminMonitoring.tsx` (the old ungated page) is now dead code. The regression test asserts: lecturer → `/admin/templates` redirects to `/dashboard`, the "Template-Freigaben" H1 is absent, and the approval-queue fetch never fires.
-A user authenticated with `roles=["lecturer"]` (no admin role) who navigates directly to `/admin` sees:
+### Original finding (for the record)
+
+`AdminMonitoring.tsx` had no role gate. `App.tsx` wired `` at `/admin` for any authenticated user. The Sidebar nav link IS gated (`Sidebar.tsx` checks `isAdmin`), but that is only a UI affordance, not a security boundary.
+
+A user authenticated with `roles=["lecturer"]` (no admin role) who navigated directly to `/admin` saw:
- the full `Administration` heading and tabbed UI;
- `getAllDeployments(null)` is fired — returns the **global** deployment list (every lecturer's work), not just their own;
diff --git a/cypress/TEST-MATRIX-DELTA.md b/cypress/TEST-MATRIX-DELTA.md
new file mode 100644
index 0000000..a95fe98
--- /dev/null
+++ b/cypress/TEST-MATRIX-DELTA.md
@@ -0,0 +1,68 @@
+# Cypress Delta Test Matrix — staging update (2026-07)
+
+> Ergänzung zur ursprünglichen `TEST-MATRIX.md`. Erfasst (A) Reparaturen bestehender Tests nach dem großen staging-Update (~7.800 geänderte src-Zeilen) und (B) neue Feature-Tests. Konventionen wie zuvor: Keycloak gestubbt, alle API via `cy.intercept` + Fixtures, keine festen Waits, stabile Selektoren.
+
+## Kontext des Updates
+
+Zwischen der Test-Baseline (`d1af1a7`) und `origin/staging` kamen u.a. hinzu: Student-Self-Service, Lecturer-Verwaltung (Admin), Admin-Rework (Split von `/admin` in `/admin/projects|templates|lecturers` mit `ProtectedRoute`), Redeploy/VM-Config-Override, Kurs-Filter, 36-Monate-Laufzeit, Expiry-Farbstufen, Owner-Only-Gating für Deployment-Aktionen, `NoRolePage`, `MobileTopBar`, `TemplateIconUpload`, `CredentialInstanceCard`.
+
+## Teil A — Reparaturen (erledigt)
+
+| Test | Ursache der Regression | Fix | Status |
+| --- | --- | --- | --- |
+| `auth-admin-bypasses-setup-gate` | `/admin` existiert nicht mehr (Split) | Ziel-Route → `/admin/projects`, Heading → „Projektübersicht" | ✅ e762bc2 |
+| `admin-approve-template-version` | Approval-Queue nach `AdminTemplateApprovals` (`/admin/templates`) verschoben | `cy.loginAs("admin","/admin/templates")` | ✅ a3d1705 |
+| `detail-delete-confirmation-flow` | Owner-Only-Gate (`fix/126`): Delete-Button ohne `owner_id` disabled | `owner_id: user-lec-1` in Fixture | ✅ a14815f |
+| `auth-non-admin-cannot-access-admin-route` | War geskippt (Befund); jetzt via `ProtectedRoute requireAdmin` behoben | Neu geschrieben: Redirect `/admin/templates`→`/dashboard`, reaktiviert | ✅ 265a90d |
+| `detail-issue-207-ungrouped-members` (fremd) | CourseGroupsCard-Rendering/Selektor geändert | in Arbeit (Sub-Agent) | 🔧 |
+
+## Teil B — Neue Feature-Tests (geplant, P0/P1-Tiefe wie Bestand)
+
+```
+Bereich
+└── Funktion
+ └── Testfall [Prio · Typ] — Ziel · Risiko bei Regression
+```
+
+### student · self-service
+- `student-dashboard-lists-deployments` **[P0 · success]** — Pure Student → `/student/dashboard`, `GET /api/v1/student/deployments` rendert Karten. Risiko: Studenten sehen ihre Umgebungen nicht.
+- `student-dashboard-empty-state` **[P1 · edge]** — leere Liste → „Aktuell keine Deployments für dich verfügbar." Risiko: kaputter Erstkontakt.
+- `student-role-routing-guard` **[P0 · permission]** — Pure Student auf `/dashboard`/`/appstore` → Redirect `/student/dashboard`. Risiko: Student sieht Lecturer-UI.
+- `student-deployment-details-credentials` **[P1 · success]** — `/student/deployment/:id` lädt Credentials, `CredentialInstanceCard` (mode=student) rendert. Risiko: Zugangsdaten unerreichbar.
+
+### admin · lecturer-management
+- `lecturer-management-lists` **[P1 · success]** — `/admin/lecturers`, `GET /api/v1/lecturers` Tabelle. Risiko: Admin-Verwaltung tot.
+- `lecturer-delete-cascade-poll` **[P1 · success]** — Detail-Dialog → „Account löschen" → Type-to-confirm → DELETE 202 → Poll 200→404 → Erfolg. Risiko: Cascade-Delete-Feedback bricht.
+
+### admin · project-overview
+- `admin-project-overview-renders` **[P1 · success]** — `/admin/projects`, Stat-Cards + View-Selector. Risiko: globale Übersicht kaputt.
+
+### admin · template-approvals (reject)
+- `admin-reject-template-with-reason` **[P1 · success]** — „Ablehnen" → Reason → POST `/reject` `{reason}`. Risiko: Autoren ohne Feedback.
+
+### deployment · redeploy
+- `redeploy-deployment-config-override` **[P1 · success]** — „Neu deployen" (running+owner) → Param ändern → POST `/redeploy` mit `deployment_parameter_overrides`+`preserve_credentials`. Risiko: Redeploy-Feature tot.
+- `redeploy-only-owner-running` **[P1 · permission]** — Button disabled für Nicht-Owner / nicht-running. Risiko: unautorisierter Redeploy.
+
+### courses · course-filters
+- `course-filters-toggle-chip` **[P1 · state]** — Chip klicken filtert Kursliste client-seitig. Risiko: Filter unbenutzbar.
+- `course-filters-admin-crud` **[P1 · success]** — Admin: Filter hinzufügen (POST) / löschen (DELETE). Risiko: Filterverwaltung kaputt.
+- `course-filters-lecturer-readonly` **[P2 · permission]** — Lecturer sieht keine Add/Edit/Delete-Controls. Risiko: Nicht-Admin ändert Filter.
+
+### wizard · runtime
+- `wizard-36-months-runtime` **[P2 · edge]** — 36-Monate-Option („3 Jahre") wählbar, `runtime_months:36` im POST. Risiko: neue Laufzeit nicht buchbar.
+
+### dashboard · expiry-tiers
+- `dashboard-expiry-color-tiers` **[P2 · state]** — critical/warning/expired via dynamischen `expires_at` zeigen korrektes Icon. Risiko: verpasste Ablaufwarnungen.
+
+### cross-cutting · mobile-nav
+- `mobile-topbar-drawer-nav` **[P2 · state]** — `cy.viewport(375,667)` → Hamburger öffnet Sidebar-Drawer. Risiko: Mobile-Navigation kaputt.
+```
+
+## Priorisierung
+
+- **P0**: student-dashboard-lists, student-role-routing-guard (Kern-Sicherheit + Kernpfad des neuen größten Bereichs)
+- **P1**: student-details/-empty, lecturer-mgmt (2), admin-project-overview, admin-reject, redeploy (2), course-filters (2)
+- **P2**: course-filters-readonly, 36-months, expiry-tiers, mobile-nav
+
+Umfang orientiert sich am Bestand (P0/P1-Tiefe): kritische Happy-Paths + wichtigste Guards/Fehlerfälle pro Feature, keine erschöpfende Rollen-/Edge-Matrix.
diff --git a/cypress/e2e/admin/admin-approve-template-version.cy.ts b/cypress/e2e/admin/admin-approve-template-version.cy.ts
index 0c9c57d..b41b5e2 100644
--- a/cypress/e2e/admin/admin-approve-template-version.cy.ts
+++ b/cypress/e2e/admin/admin-approve-template-version.cy.ts
@@ -2,9 +2,9 @@
// admin-approve-template-version
// ──────────────────────────────
-// P0 success-path test: an admin on /admin sees the Template-Freigaben card
-// populated with pending public template-versions (loaded via GET
-// /api/v1/template-versions/queue). Clicking "Schnell genehmigen" on a row
+// P0 success-path test: an admin on /admin/templates sees the Template-
+// Freigaben card populated with pending public template-versions (loaded via
+// GET /api/v1/template-versions/queue). Clicking "Schnell genehmigen" on a row
// must fire POST /api/v1/template-versions//approve and the row must
// disappear from the queue UI on success.
//
@@ -15,11 +15,13 @@
// versions pile up indefinitely and the AppStore stops growing — both real
// user-visible regressions.
//
-// AdminMonitoring.tsx renders one card per pending TemplateVersionQueueItem
-// (template name + version pill + "Schnell genehmigen" / "Details prüfen"
-// buttons). handleApprove() calls approveTemplateVersion() from
-// src/api/github.ts and then removes the version from local state. The
-// per-row removal is the user-visible signal that the action succeeded.
+// AdminTemplateApprovals.tsx (route /admin/templates, split out of the old
+// AdminMonitoring.tsx during the admin rework) renders one card per pending
+// TemplateVersionQueueItem (template name + version pill + "Schnell
+// genehmigen" / "Ablehnen" buttons). handleApprove() calls
+// approveTemplateVersion() from src/api/github.ts and then removes the
+// version from local state. The per-row removal is the user-visible signal
+// that the action succeeded.
//
// Fixture
// queue-pending.json mirrors the QueueResponse shape from
@@ -53,7 +55,7 @@ describe("Admin · approves a pending template-version from the queue", () => {
});
it("clicks 'Schnell genehmigen' on the first pending version and the row disappears", () => {
- cy.loginAs("admin", "/admin");
+ cy.loginAs("admin", "/admin/templates");
// The queue load is the load-bearing fetch for the Template-Freigaben
// card. Waiting on it proves the GET fired AND that the response landed
@@ -77,7 +79,7 @@ describe("Admin · approves a pending template-version from the queue", () => {
}).as("getQueueEmpty");
// Click the "Schnell genehmigen" button scoped to the WordPress row.
- // The button text is exact (AdminMonitoring.tsx line ~900). We scope to
+ // The button text is exact (AdminTemplateApprovals.tsx:350). We scope to
// the row by locating the template-name