Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
12 KiB
phase, plan, quick_id, subsystem, tags, status, requires, provides, affects, key-files, decisions, duration, completed, commits, plan_head_before, plan_head_after, actuals
| phase | plan | quick_id | subsystem | tags | status | requires | provides | affects | key-files | decisions | duration | completed | commits | plan_head_before | plan_head_after | actuals | |||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260929-9wc | 01 | 260929-9wc | api, web, prisma |
|
complete |
|
|
|
|
ca. 10 Minuten reine Ausfuehrung | 2026-09-29 | 3 | 643b1a2caa |
e48c0de238 |
|
Phase quick-260929-9wc Plan 01: Eigene Module Summary
Administratoren binden externe https-Seiten als Seitenleisten-Eintraege ein (Name, Adresse, Kategorie); alle Benutzer sehen sie unter der Kategorie, ein Klick zeigt die Seite in einem Rahmen mit dem XFrame-Sandbox-Wert und immer sichtbarem Knopf „In neuem Tab öffnen“. Daten liegen mandantengetrennt mit Zeilenschutz in der neuen Tabelle CustomModule.
Was gebaut wurde
Aufgabe 1 (Tracer), Commit b9d87be
MODULE_CATEGORIES(fuenf Kennungen) + TypModuleCategoryinpackages/shared; Gleichlauf-Spec gegenmoduleCategoriesin de.json/en.json.- Prisma-Modell
CustomModuleund handgeschriebene Migration20260929120000_custom_module(ENABLE/FORCE RLS,tenant_isolation_policyohne Benutzerdimension, bewusst keinesystem_read_policy). - API
/custom-modules: DTO (Name 1 bis 100, Adresse nur https ohne Zugangsdaten, max 2048, Kategorie per@IsIn), Dienst (je Methodeconst tenantPrisma = forTenant(this.prisma, tenantId),row.tenantId-Pruefung, 404 bei fremd/unbekannt), Controller (listvorgetOne, schreibende Routen@Roles(ADMIN, SUPER_ADMIN)), Modul inapp.module.ts. - Zugriffsklassifikation nachgemessen fortgeschrieben (siehe Zahlen).
- Web:
custom-modules-api.ts(inkl.checkCustomModuleUrl), Seitenleiste mit vereinigter Eintragsliste (Gruppierung, Suche, eingeklappte Kacheln, Auswahlmarke, Kopfzeilen-Titel ueberuseNavStoremit slug = id),CustomModuleView+ Seite/modules/custom/[id], TextecustomModulesde/en. - Lokal migriert (Container-IP,
prisma migrate deploy), API neu gebaut; curl-Durchstich bestanden.
Aufgabe 2, Commit e7fc4de
- Verwaltungsseite
admin/custom-modules(Liste, Leer-Zustand, Anlegen/Bearbeiten-Dialog mit Adresspruefung vor dem Senden, Loeschen mit Rueckfrage), Aufruf vonbumpSidebarRefreshnach jedem erfolgreichen Speichern/Loeschen, Admin-Leisten-Eintrag hinter „Module“, Texteadmin.customModulesundheader.admin.customModulesde/en.
Aufgabe 3, Commit e48c0de
- CHANGELOG-Eintrag unter „Unveröffentlicht“ > „Neu“, alle Tore, Stack neu gebaut.
Tore (gemessen)
| Tor | Ergebnis |
|---|---|
| Web-Tests vollstaendig | 102 Dateien, 992 Tests, alle gruen |
| API-Tests vollstaendig | 88 Dateien, 1495 Tests, alle gruen |
pnpm turbo run type-check lint |
9/9 Aufgaben erfolgreich |
| Biome-Warnungen Web | 55 (Grundlinie 55) |
| Biome-Warnungen API | 82 (Grundlinie 82) |
| rls-coverage.spec / rls-access-inventory.spec | gruen (30 Zusicherungen im Inventar) |
prisma migrate deploy (zweiter Lauf) |
„No pending migrations to apply.“ |
prisma migrate diff |
enthaelt „CustomModule“ nicht |
| curl-Durchstich | Anlegen, Liste, Einzelabruf ok; http 400; anonym 401; Loeschen; danach 404 |
| Stack | web :3000/login 200, api /health ok, GET /custom-modules anonym 401 |
| Nicht gepusht | git branch -r --contains HEAD leer |
Zugriffsklassifikation nachgemessen (Gate-Schleife, nur .ts ohne spec):
- Summe vorher gemessen 61/217/6 (Dokument nannte 61/216/6); Drift in
user: gemessen 18 gebunden statt 17 (aus quick-260928-ujj), korrigiert. custom-modules: 0/7/0 (list 1, getOne 1, create 1, update 2, remove 2).- Neue Summe: 61/224/6.
- Klassen-Verteilung: Ueberschrift/Tabelle nannten 77 Paare/40 muss, Bestandsaufnahme hatte schon 78/41 (
grep -cE '^\| apps/api/src/'); nach neuem Eintrag 79 Paare, davon 42 muss, 21 keine-mandantengebundene-tabelle, 14 beides, 2 bewusst-uebergreifend. Nachtrag-Absatz „quick-260929-9wc“ ergaenzt.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Endlosschleife beim Laden der Verwaltungsseite
- Found during: Aufgabe 2 (Test zaehlte 5 statt 2 Listenabrufe)
- Issue:
fetchModuleshing peruseCallbackant(Uebersetzungsfunktion); ein Mock liefert je Render eine neue Funktion, der Effekt lief erneut. Auch mit echtem next-intl fragil. - Fix: Fehler als Boolean
loadFailedgefuehrt, Text erst im JSX uebersetzt;fetchModulesohne Abhaengigkeit. - Files modified:
apps/web/src/app/(portal)/admin/custom-modules/page.tsx - Commit:
e7fc4de
2. [Rule 3 - Blocking] Layout-Waechter der Modulordner
- Found during: Aufgabe 3 (voller Web-Testlauf)
- Issue:
module-layouts.test.tsx(T-e8k-04) verlangt in jedem nicht-dynamischen Ordner untermodules/einelayout.tsxmit ModuleAccessGate; der neue Ordnercustom/ist bewusst fuer alle sichtbar (D-01) und hat keine Schranke. - Fix: Explizite, begruendete Ausnahmeliste
DIRS_WITHOUT_GATE = ['custom']im Test, statt eine wirkungslose Durchreich-Layout-Datei anzulegen. - Files modified:
apps/web/src/app/(portal)/modules/module-layouts.test.tsx - Commit:
e48c0de
3. Plan-Feinheit (kein Regelfall): Die Seitenleisten-Fehlerbehandlung fuer listCustomModules laesst bei Fehler den bisherigen Stand stehen (leer beim ersten Laden), wie der Modulabruf, statt aktiv zu leeren; Ergebnis beim ersten Laden identisch mit „leere Liste“.
Bewusst offen
- Gruppen-Einschraenkung fuer eigene Module (D-02) zurueckgestellt.
ModuleGrant.moduleIdist ein Pflicht-Fremdschluessel aufModule(onDelete: Cascade); eigene Module sind keineModule-Zeilen. Eine Einschraenkung braeuchte eine neue Freigabetabelle oder einen Umbau vonModuleGrantsamtmodule-access.service.tsund der Admin-Freigabeoberflaeche, also nicht „sehr wenig Aufwand“. Im Code nichts dafuer gebaut.
Browser-Pruefung offen (Orchestrator)
Playwright MCP steht in diesem Ausfuehrungskontext nicht zur Verfuegung. Der Orchestrator fuehrt die Pruefung durch: web :3000, Anmeldung admin / admin123, echte Navigation (browser_navigate), nie per fetch() aus der Seite messen, zuerst ueber den Theme-Knopf der Kopfzeile auf dunkel schalten. Stack ist neu gebaut und laeuft.
- Verwaltung > „Eigene Module“ (neuer Eintrag in der Admin-Leiste hinter „Module“, „Module“ dabei nicht markiert): Leer-Zustand mit Knopf „Eigenes Modul anlegen“.
- Anlegen mit Name „Beispielseite“, Adresse
http://example.com-> Meldung, nichts gespeichert; dannhttps://user:pw@example.com-> Meldung; dannhttps://example.com, Kategorie „Infrastruktur“ -> gespeichert, Tabelle zeigt den Eintrag, die Seitenleiste zeigt „Beispielseite“ unter „Infrastruktur“ OHNE Neuladen. - Zweiter Eintrag „GitHub“,
https://github.com, Kategorie „Sicherheit“ -> erscheint unter „Sicherheit“. - Klick auf „Beispielseite“:
/modules/custom/<id>, Kopfzeilen-Titel „Beispielseite“, Auswahlmarke am Eintrag, Rahmen fuellt den Inhaltsbereich ohne doppelten Rollbalken, „In neuem Tab öffnen“ sichtbar; iframe traegt den Sandbox-Wertallow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandboxundreferrerpolicy="no-referrer"; der Link oeffnet einen neuen Tab mit example.com. - Klick auf „GitHub“: Rahmen zeigt die Einbettungssperre des Browsers, „In neuem Tab öffnen“ ist trotzdem sichtbar und funktioniert.
- Seitenleiste eingeklappt: beide Eintraege als Kachel mit Namen im Tooltip; Suche „Beisp“ findet den Eintrag.
- Bearbeiten: „Beispielseite“ in „Beispiel“ umbenennen -> Seitenleiste zieht sofort nach. Loeschen mit Rueckfrage -> Eintrag verschwindet aus Tabelle und Seitenleiste; alte Adresse
/modules/custom/<id>zeigt „Dieses Modul gibt es nicht mehr.“ - Sprache auf Englisch: keine rohen Uebersetzungsschluessel auf Verwaltungsseite und Rahmen-Seite.
- Screenshots (dunkel) von Verwaltungsseite, Seitenleiste mit Eintraegen und Rahmen-Seite ablegen; danach die Testeintraege loeschen, damit die lokale Datenbank sauber bleibt.
Hinweis: Der curl-Durchstich hat seinen Testeintrag bereits geloescht; die lokale Datenbank enthaelt keine eigenen Module.
Known Stubs
Keine.
Threat Flags
Keine neue Angriffsflaeche ausserhalb des Plan-Bedrohungsmodells (T-9WC-01 bis 07 umgesetzt: Rollen-Metadaten per Spec geprueft, tenantId nur aus req.tenantId, https-Regel in API und Web, exakter Sandbox-Wert, Referrer/rel, MaxLength, whitelist: true).
Nichts gepusht
Drei lokale Commits (b9d87be, e7fc4de, e48c0de), kein git push; die vorgemerkten Loeschungen von .planning/.continue-here.md und .planning/HANDOFF.json blieben unangetastet im Index.
Self-Check: PASSED
- Dateien vorhanden: Migration,
custom-modules.service.ts/controller.ts/module.ts/dto,custom-modules-api.ts,custom-module-view.tsx,modules/custom/[id]/page.tsx,admin/custom-modules/page.tsxmit Komponenten (alle im Commit-Stat sichtbar). - Commits vorhanden:
b9d87be,e7fc4de,e48c0de(git log),commits: 3gemessen ueberrev-listvom Ledger.
Browser-Pruefung (Orchestrator, 29.09., dunkel)
Durchgefuehrt per Playwright MCP auf :3000, Theme per Kopfzeilen-Knopf auf „Dunkel“:
- Verwaltung > „Eigene Module“: Eintrag in der Admin-Leiste, Leer-Zustand korrekt.
- http://example.com -> „Bitte geben Sie eine Adresse ein, die mit https:// beginnt.“; https://user:pw@example.com -> „Die Adresse darf keinen Benutzernamen und kein Kennwort enthalten.“; https://example.com / Infrastruktur -> gespeichert, Seitenleiste zeigt „Beispielseite“ ohne Neuladen.
- „GitHub“ / Sicherheit erscheint unter „Sicherheit“.
- Rahmen-Seite: Kopfzeilen-Titel, Auswahlmarke, kein doppelter Rollbalken; sandbox =
allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox, referrerpolicy = no-referrer; Link target=_blank rel="noopener noreferrer". - GitHub: Einbettung per frame-ancestors blockiert, „In neuem Tab öffnen“ oeffnet github.com im neuen Tab.
- Eingeklappt: Eintraege als Symbole; Suche „Beisp“ findet den Eintrag.
- Umbenennen zieht Seitenleiste sofort nach; Loeschen mit Rueckfrage; alte Adresse zeigt „Dieses Modul gibt es nicht mehr.“
- Englisch: nicht per Oberflaeche umgeschaltet; stattdessen Schluessel-Paritaet de/en geprueft (keine fehlenden Schluessel, alle Texte ueber t()).
- Testeintraege geloescht, lokale DB ohne eigene Module.