# DEVLOG – Laufzettel Entwicklungstagebuch mit Zeiterfassung. Eine Sitzung ist ein Abschnitt; Sub-Agenten-Läufe stehen als Unterpunkte mit Aufgabe und Dauer. ## Zeitübersicht | Issue | Titel | Tatsächlich | |---|---|---| | #005 | M0 – Gerüst, Prozess, Design-Token | 1 h 25 min | | #006 | M1 – Datenmodell | 1 h 10 min | | | **Summe** | **2 h 35 min** | | Meilenstein | Tatsächlich | Status | |---|---|---| | v0.1.0 | 2 h 35 min | in Arbeit | --- ## 2026-09-24 · Sitzung 1 · 22:05–23:30 **Issue:** #005 (M0) **Version:** 0.1.0 ### Erledigt - Umgebung geprüft: Node v24.21.0 nur über nvm (nicht im `PATH` nicht-interaktiver Shells), Docker 29.1.3, Chromium-Systembibliotheken vorhanden, kein `pandoc` - Gitea aufgesetzt: 11 Labels, 5 Meilensteine (`v0.1.0`–`v0.5.0`), 19 Issues (#1–#19), Nummern passen zu den Dateinamen unter `docs/issues/` - Projektgerüst angelegt und alle vier Schranken grün: `lint`, `typecheck`, `test`, `build` - Betriebsstand gehärtet und der Nachweis in `tests/betriebsstand.test.ts` festgeschrieben - Abnahme von #005 wirklich gefahren, nicht behauptet: `docker compose up -d --build` startet den Stack, `migrate` läuft und beendet sich, `web` wartet korrekt darauf, `/api/gesundheit` meldet `ok` — und `5432` ist vom Host nicht erreichbar, aus `web` schon - `scripts/pruefe-doku.mjs` gegen vier eingebaute Fehler geprüft: es findet abweichende Zeiten, fehlende Einträge, Verweise auf fehlende Dateien und fehlende Bilder ### Entscheidungen - **Deutsch im Code, auch für Bezeichner.** Der Auftragstext verlangte Englisch; Florian hat sich für Einheitlichkeit mit ProxLedger entschieden. Verbindliche Namensabbildung in `CLAUDE.md`. - **Kein Auth.js.** v5 steht seit Jahren auf Beta (`5.0.0-beta.32`), authjs.dev führt das Projekt im Wartungsmodus („now part of Better Auth"), die Maintainer nennen „no immediate plans for v5", und `@auth/prisma-adapter` deklariert Prisma nur bis 6 — wir fahren Prisma 7. Stattdessen `openid-client` + `jose` wie in ProxLedger. Dieselbe Überlegung wie dort, nur ein Jahr später noch deutlicher. - **`prisma` exakt auf 7.10.0 gepinnt.** Der `latest`-Tag von `prisma` zeigt auf `8.0.0-rc.17`, der von `@prisma/client` auf `7.10.0`. Ein unbedachtes `pnpm add prisma` hätte einen Versions-Konflikt erzeugt. - **`eslint` bleibt auf 9.x.** Mit eslint 10 bricht das Regelwerk beim Laden ab: `eslint-config-next` bringt `eslint-plugin-react@7.37.5` mit, das `eslint` nur bis `^9.7` unterstützt, und eine neuere Fassung des Plugins gibt es nicht (letzte Veröffentlichung April 2025). Empirisch festgestellt, nicht geraten. - **`typescript` bleibt auf `~6.0.3`.** `typescript-eslint@8.70.1` erlaubt nur `>=4.8.4 <6.1.0`; TypeScript 7.0.2 ist zwar `latest`, aber noch nicht unterstützt. - **`@node-rs/argon2` statt `argon2`.** Liefert vorgebaute musl- und gnu-Binaries, braucht auf `node:24-alpine` also keine C++-Toolchain in der Bau-Stufe. - **`barcode-detector` statt `jsQR`.** `jsqr` ist seit April 2021 unveröffentlicht. Der Polyfill bietet dieselbe API wie das native `BarcodeDetector`, also ein Codepfad statt zwei. - **Seed läuft nicht automatisch.** Die Migrationen schon, der Seed bleibt ein dokumentierter Handgriff — so steht es in den Abnahmekriterien. - **shadcn/ui eingerichtet, aber der Button folgt der Vorgabe, nicht dem Preset.** Der Vertrag von shadcn (`--background`, `--primary`, `--border`, …) wird in `globals.css` aus den Laufzettel-Token gespeist; jedes später kopierte Bauteil sieht damit sofort richtig aus und folgt auch dem hellen Modus. Der Scannen-Knopf nutzt den shadcn-Button, überschreibt aber Form und Höhe (Pille, 44 px am Handy statt 32 px). - **PDF der Anleitung über Playwright.** `pandoc` und `wkhtmltopdf` fehlen und sind ohne root nicht installierbar; Chromium ist für die E2E-Tests ohnehin da. ### Probleme - **Projektverzeichnis gehörte `root`.** `~/Projekte/Laufzettel` war root-eigen, die Sitzung läuft als `florianwilde`, und `sudo` verlangt hier ein Passwort. Bis Florian den `chown` gemacht hat, wurde das Gerüst im Scratchpad vorgebaut und danach hineinverschoben — die Gitea-Arbeit brauchte das Verzeichnis nicht und lief parallel. - **pnpm 12 hat die Einstellung für Build-Skripte umbenannt.** `onlyBuiltDependencies` wird noch von `pnpm config list` angezeigt, aber nicht mehr angewandt; wirksam ist `allowBuilds` als Zuordnung in `pnpm-workspace.yaml`. Das `pnpm`-Feld in `package.json` liest pnpm 12 gar nicht mehr. Gefunden über `pnpm approve-builds --all`, das die richtige Form selbst schreibt. - **Context7-MCP ist nicht autorisiert**, der OAuth-Ablauf geht in dieser Sitzung nicht. Alle Versionen wurden stattdessen direkt gegen das npm-Registry und die offiziellen Docs geprüft. - **pnpm 12 prüft die Lieferkette und lehnt taufrische Pakete ab.** `vite@8.3.1` (8,7 h alt) und `lucide-react@1.48.0` (15,3 h alt) fielen durch. Beide auf die Vorgängerfassung gesetzt, die Richtlinie bleibt an. Bei der Gelegenheit alle direkten Abhängigkeiten auf ihr Alter geprüft. - **`shadcn init` lässt sich nicht skripten.** Es fragt auch mit `--yes` nach einem Preset und bleibt hinter einer Pipe stumm hängen. `shadcn add --yes --overwrite < /dev/null` geht dagegen. Zusätzlich nötig: Pfad `"cn": ["./src/lib/utils"]` in `tsconfig.json` und das Paket `radix-ui`, beides schreibt `init` nicht. - **Port 3000 ist von ProxLedger belegt.** Die Abnahme lief auf 3100 und 3200. - **Kein Gitea-Actions-Runner registriert** (0 Runner). `.gitea/workflows/ci.yml` wird trotzdem angelegt und ist nutzbar, sobald ein Runner da ist; verbindlich sind bis dahin der Pre-Commit-Hook und die Durchläufe vor jedem Commit. ### Offen - Der `pct create`-Teil des Proxmox-Skripts (#019) lässt sich hier nicht ausführen, weil kein PVE-Host erreichbar ist. Geplant: `shellcheck` plus `--dry-run`, und `deploy/install.sh` vollständig in einem `debian:13`-Container geprüft. Der Lauf auf echtem Proxmox braucht eine Gegenprüfung durch Florian, dafür kommt eine Prüfliste. - Der OpenLDAP-Testcontainer wandert von #005 nach #016, wo er tatsächlich benutzt wird — ein Dienst, den noch kein Test anfasst, wäre eine Attrappe. ### Nächster Schritt - #006 Datenmodell: das vollständige Schema in deutscher Benennung, `tagCode`-Generator, Krypto-Helfer, erste Migration, idempotenter Seed-Rumpf. --- ## 2026-09-25 · Sitzung 2 · 00:05–01:15 **Issue:** #006 (M1) **Version:** 0.1.0 ### Erledigt - Vollständiges Schema in deutscher Benennung: 20 Tabellen, 9 Aufzählungen, eine Migration - `src/lib/tagcode.ts` und `src/lib/geheimnisse.ts` samt Tests, parallel von einem Sub-Agenten gebaut, während ich das Schema schrieb (Dauer dort: rund 10 min) - Idempotenter Seed für Betriebsmodus, eigene Organisation und Administrator — zweimal hintereinander gefahren, keine Zeile geändert - Unabhängiger Review-Agent gegen die Akzeptanzkriterien; alle Blocker und alle neun „sollte behoben werden"-Punkte abgearbeitet - **106 Tests** grün (vorher 11), davon 42 gegen die echten Datenbankregeln ### Entscheidungen - **Regeln gehören zusätzlich in die Datenbank.** Zwölf CHECK-Bedingungen, zwei partielle Eindeutigkeitsindizes und vier zusammengesetzte Fremdschlüssel. Begründung: die Service-Schicht ist nicht der einzige Weg, auf dem Daten entstehen — Migrationen, `psql`, Skripte und ab Phase 4 die Connectoren schreiben an ihr vorbei. - **Die Mandantengrenze wird strukturell erzwungen.** `Lauf` verweist mit `("geraetId", "kundeId")` auf `Geraet("id", "kundeId")`, analog für Person und Abteilung. `docs/architektur.md` nennt die Grenze eine Sicherheitsgrenze — dann soll sie nicht allein an `kundeErmitteln()` hängen. `MATCH SIMPLE` lässt einen Lauf ohne Gerät weiter zu. - **Kein Cascade dort, wo das Archiv hängt.** Ein `DELETE` auf einen Lauf hätte Schrittergebnisse und Kommentare stillschweigend mitgenommen, ein Protokolleintrag hätte seinen Urheber verlieren können. Beides jetzt `Restrict`. - **`TagCodeVergabe` als Vergabebuch.** Die Eindeutigkeit allein aus `Geraet.tagCode` zu ziehen reicht nicht: verschwindet die Zeile, wäre der Code wieder frei und ein alter Aufkleber im Regal zeigte auf ein fremdes Objekt. - **Widerspruch im Auftrag beim tagCode-Alphabet.** Das wörtliche Alphabet enthält ein `L`, die Klammer daneben verbietet es. Aufgelöst zugunsten des wörtlichen Alphabets: die Klammer benennt Verwechslungsgruppen, keine Ausschlussliste — aus `{1, I, L}` sind `1` und `I` weg, dadurch ist `L` eindeutig. Das Akzeptanzkriterium in #006 ist richtiggestellt worden, statt einen grünen Test darüber zu setzen. - **Ein Systemlauf muss darstellbar sein.** `Lauf.erstelltVonId` ist optional, wie schon `Schrittergebnis.aktualisiertVonId`. Wiederkehrende Läufe und der Start per Webhook (Phase 4) haben keinen Menschen dahinter. ### Probleme - **`prisma migrate reset --force` zeigte den Bestätigungsdialog trotzdem** und ließ einen Schema-Engine-Prozess zurück, der die Advisory-Sperre hielt; der nächste `migrate dev` lief daraufhin in einen Timeout. Verlässlicher: `DROP SCHEMA public CASCADE` per psql, dann `migrate dev --create-only < /dev/null`. - **Port 5433 ist von ProxLedger belegt.** Die Entwicklungsdatenbank liegt jetzt auf 5434, umstellbar über `DEV_DB_PORT`. - **Eine Regex-Ersetzung hat `quelle.length` auch aus der Längenprüfung gerissen** und den Krypto-Helfer unübersetzbar gemacht. Die Testsuite hat es sofort gezeigt — ein Argument mehr dafür, nach jeder Sammeländerung wirklich alle Schranken laufen zu lassen. ### Nächster Schritt - #007 Auth, Rollen, Benutzer: `openid-client` mit Discovery, `jose`-Sitzungscookie, lokaler Login mit argon2, `RECHTE`-Tabelle, Login-Seite mit Rücksprung auf die ursprüngliche URL.