399 lines
23 KiB
Markdown
399 lines
23 KiB
Markdown
# 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 |
|
||
| #007 | M2 – Auth, Rollen, Benutzer | 1 h 35 min |
|
||
| #008 | M3 – Betriebsmodus und Kunden | 1 h 20 min |
|
||
| #009 | M4 – Vorlagen-Versionierung im Service | 1 h 20 min |
|
||
| #010 | M5 – Vorlagen-Editor | 2 h 10 min |
|
||
| | **Summe** | **9 h** |
|
||
|
||
| Meilenstein | Tatsächlich | Status |
|
||
|---|---|---|
|
||
| v0.1.0 | 9 h | 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 <bauteil> --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.
|
||
|
||
---
|
||
|
||
## 2026-09-25 · Sitzung 3 · 01:55–03:35
|
||
|
||
**Issue:** #007 (M2)
|
||
**Version:** 0.1.0
|
||
|
||
### Erledigt
|
||
|
||
- OIDC mit `openid-client` 6.8.8: Discovery, PKCE, `state`, `nonce`; API vorher gegen die
|
||
Typdefinitionen im Paket geprüft statt aus dem Gedächtnis geschrieben
|
||
- `jose`-Sitzungscookie, lokaler Login mit argon2, Rechtetabelle, JIT-Provisionierung
|
||
- Anmeldeseite, geschützter Bereich, Benutzerverwaltung, rollenabhängige Navigation
|
||
- Sub-Agent hat `rollen.ts` und `sitzung.ts` samt 85 Tests parallel gebaut (rund 6 min)
|
||
- Unabhängiger Review-Agent: ein Blocker, acht weitere Befunde, alle abgearbeitet
|
||
- **274 Tests** grün (vorher 106)
|
||
|
||
### Entscheidungen
|
||
|
||
- **Rücksprungziel wird aufgelöst, nicht mit Präfixen verglichen.** Die Präfix-Prüfung war eine
|
||
offene Weiterleitung: `/<TAB>/fremd.example` besteht `startsWith("/")`, ist kein `//` und kein
|
||
`/\` — Browser entfernen den Tabulator aber beim Verarbeiten der URL, übrig bleibt
|
||
`//fremd.example`. Das Auflösen gegen eine Wegwerf-Basis macht genau das, was der Browser tut,
|
||
und der Vergleich der Herkunft erschlägt alle Formen auf einmal.
|
||
- **Alle Umleitungen über `APP_URL`.** `nextUrl.origin` lieferte im Test `localhost` statt
|
||
`127.0.0.1`; hinter einem Reverse Proxy wäre es der interne Host. Neues Modul
|
||
`src/lib/adressen.ts`, die Herkunft der Anfrage ist nur noch Notnagel.
|
||
- **Ohne `OIDC_GROUP_*` bleibt die Rolle, wie sie ist.** Sonst wäre jede OIDC-Anmeldung eine
|
||
stille Herabstufung auf `LESER` — bei einem einzigen Administrator ohne Rückweg.
|
||
- **Verknüpfung über die Adresse nur bei `email_verified`.** Sonst wäre ein Anbieter mit frei
|
||
wählbaren Adressen der Weg, den lokalen Notfall-Administrator zu übernehmen.
|
||
- **Der letzte aktive Administrator ist geschützt**, gegen Herabstufung wie gegen Abschalten.
|
||
- **Kein `overflow-x: hidden` auf `body`.** Es sieht aus wie eine Lösung, verdeckt aber nur: zu
|
||
breiter Inhalt wird abgeschnitten statt erreichbar, und die Prüfung im Screenshot-Skript kann
|
||
ihn nicht mehr finden. Layouts müssen bei 390 px von sich aus passen.
|
||
- **`ROLLEN_TEXT` liegt in `src/lib`,** nicht in der Server-Schicht: eine Client-Komponente hätte
|
||
sonst die komplette Rechtetabelle ins Browser-Bündel gezogen.
|
||
|
||
### Probleme
|
||
|
||
- **Der Platzhalter-Hash gegen den Zeitkanal war erfunden** und hätte sofort geworfen statt Zeit
|
||
zu kosten. Die Zusage wäre damit schlicht falsch gewesen. Jetzt ein echter, lazy erzeugter
|
||
argon2-Hash; nachgemessen rund 1 ms Unterschied über je 60 Anfragen.
|
||
- **Beim Verschieben der Übersicht nach `(app)/` blieb `<Huelle>` in der Seite stehen.** Die Hülle
|
||
wurde doppelt gerendert — zwei Kopfzeilen, das innere Menü ohne Benutzer. Es war im Screenshot
|
||
zu sehen und ist mir trotzdem entgangen; gefunden hat es der Review-Agent. Ein Bild anzusehen
|
||
ersetzt nicht, gezielt nach dem zu suchen, was sich gerade geändert hat.
|
||
- **Prisma-Modelltypen kommen aus `@/generated/prisma/client`,** nicht aus `.../models` — dort
|
||
liegen nur die Hilfstypen der Abfragen. Steht jetzt in `CLAUDE.md`.
|
||
- **`networkidle` als Wartebedingung in Playwright** läuft bei einer Anwendung mit offenen
|
||
Verbindungen in den Timeout. `load` plus geladene Schriften ist deterministisch.
|
||
|
||
### Nächster Schritt
|
||
|
||
- #008 Betriebsmodus und Kunden: `betriebsmodus()`, `kundeErmitteln()`, Moduswechsel in beiden
|
||
Richtungen, Kundenverwaltung, Admin-Einstellungen.
|
||
|
||
---
|
||
|
||
## 2026-09-25 · Sitzung 4 · 05:35–07:00
|
||
|
||
**Issue:** #008 (M3)
|
||
**Version:** 0.1.0
|
||
|
||
### Erledigt
|
||
|
||
- `src/server/mandant.ts` als die eine Stelle für die Mandantenregel
|
||
- Moduswechsel in beiden Richtungen, Kundenverwaltung (Sub-Agent, 25 Tests), Admin-Einstellungen
|
||
- Wechsel per Playwright **durch die Oberfläche** gefahren, nicht nur im Service: Navigation
|
||
wechselt sofort, `/kunden` wird erreichbar
|
||
- Unabhängiger Review: drei Blocker, sechs weitere Befunde, alle abgearbeitet
|
||
- **336 Tests** grün (vorher 274)
|
||
|
||
### Entscheidungen
|
||
|
||
- **Die Sichtbarkeit gehört in `kundeErmitteln`,** nicht in eine zweite Funktion daneben. Sonst
|
||
müsste jeder künftige Service daran denken — und #009 ff. übernehmen diese Signatur.
|
||
- **Lesemodelle filtern selbst.** Sie verlangen den Benutzer, statt sich auf den Aufrufer zu
|
||
verlassen. `undefined` heißt „keine Einschränkung“, `[]` heißt „sieht nichts“; die Umsetzung
|
||
steht an einer Stelle, und beide Fälle haben einen Test.
|
||
- **`pg_advisory_xact_lock` statt Serializable.** Der Wechsel ist selten und kurz; ein Schloss auf
|
||
Transaktionsebene ist verständlicher als eine Isolationsstufe, die man beim Lesen übersieht, und
|
||
wird auch bei einem Abbruch freigegeben.
|
||
- **Die Prüfung läuft zweimal** — einmal für den Dialog, einmal innerhalb der Transaktion. Beide
|
||
Male derselbe Code, damit es keine nachgebaute zweite Wahrheit gibt.
|
||
- **ASCII-Faltung gilt für Bezeichner, nicht für Fließtext.** Steht jetzt ausdrücklich in
|
||
`CLAUDE.md`; Protokoll-Aktionen sind entsprechend nachgezogen.
|
||
|
||
### Probleme
|
||
|
||
- **Ich habe in einem Kommentar etwas behauptet, das nicht stimmte** („Alles in einer
|
||
Transaktion"), während der Protokolleintrag dahinter stand. Ein Kommentar, der eine Zusage
|
||
macht, gehört genauso geprüft wie der Code darunter.
|
||
- **`sichtbareKundenIds` hatte keinen einzigen Aufrufer.** Eine Funktion zu schreiben, die eine
|
||
Regel durchsetzt, und sie dann nirgends zu rufen, sieht im Review aus wie Absicht und ist es
|
||
nicht. Der Volltextlauf über `src` hätte das in zehn Sekunden gezeigt.
|
||
- **Der Wettlauf war reproduzierbar**, nicht theoretisch: zwei gleichzeitige Wechsel liefen beide
|
||
durch.
|
||
|
||
### Nächster Schritt
|
||
|
||
- #009 Vorlagen-Versionierung: Veröffentlichen, tiefe Kopie als Entwurf, höchstens ein Entwurf,
|
||
Unveränderlichkeit, JSON-Export und -Import.
|
||
|
||
---
|
||
|
||
## 2026-09-25 · Sitzung 5 · 07:00–08:20
|
||
|
||
**Issue:** #009 (M4)
|
||
**Version:** 0.1.0
|
||
|
||
### Erledigt
|
||
|
||
- `src/server/services/vorlagen.ts`: Anlegen, Kopfdaten bearbeiten, Entwurf holen (tiefe Kopie),
|
||
Speichern, Veröffentlichen, Verwerfen, Duplizieren, Archivieren
|
||
- `src/server/services/vorlagen-austausch.ts` mit `docs/vorlagen-format.md`: ein Zod-Schema für
|
||
Editor, Export und Import — eine Vorlage zieht als Datei zwischen zwei Installationen um
|
||
- `src/lib/validierung.ts` als einzige Quelle für Zod samt deutscher Meldungen, mit ESLint-Regel
|
||
- Migration `vorlagen_unveraenderlich`: vier Trigger, die die Unveränderlichkeit in der Datenbank
|
||
durchsetzen — nicht nur im Service
|
||
- Unabhängiger Review: drei Blocker (alle an der Mandantengrenze), acht weitere Befunde, alle
|
||
abgearbeitet
|
||
- **428 Tests** grün (vorher 336), davon neu: Mandantensicht, Datenbankregeln, Gleichzeitigkeit,
|
||
Austausch mit unsauberen Daten
|
||
|
||
### Entscheidungen
|
||
|
||
- **Die Regel steht zweimal: im Service und in der Datenbank.** Der Service gibt die verständliche
|
||
Meldung, die Datenbank die Garantie. Alles, was nicht durch den Service geht — Migrationen,
|
||
`psql`, der Seed, ab Phase 4 die Connectoren —, läuft sonst an der einen Regel vorbei, die das
|
||
Archiv überhaupt erst etwas wert macht. Die doppelte Haltung kostet einen Trigger und spart die
|
||
Annahme, dass jeder künftige Schreibweg daran denkt.
|
||
- **Ein Entwurf je Vorlage kommt vom partiellen Index**, nicht von einer Prüfung davor. Zwei
|
||
gleichzeitige Klicks auf „Bearbeiten“ laufen damit unvermeidlich in `P2002` — und genau dort
|
||
wiederholt der Service und gibt beiden denselben Entwurf zurück, statt einen Fehler zu zeigen,
|
||
den niemand versteht.
|
||
- **Optimistisches Sperren über `geaendertAm`,** nicht über eine Zählerspalte. Der Zeitstempel ist
|
||
ohnehin da, und `updateMany` mit Bedingung erledigt den Vergleich in derselben Anweisung.
|
||
- **Unbekannte `konfig`-Schlüssel überleben, unbekannte Werte bekannter Schlüssel nicht.** Ein
|
||
neues Feld aus einer späteren Fassung darf einen Umzug nicht verhindern; eine unbekannte `shell`
|
||
aber entscheidet darüber, wie ein Wert maskiert wird — sie stillschweigend zu verwerfen hieße,
|
||
einen bash-Befehl nach PowerShell-Regeln zu maskieren, und das kann auf einem Server Daten
|
||
kosten. Also laut abweisen, mit Pfad und erlaubten Werten.
|
||
- **Der Export ist nachsichtig, der Import streng.** Herausgeben muss man auch, was nicht ins
|
||
Schema passt, sonst verliert die Datei Inhalt. Annehmen darf man es nicht.
|
||
- **`tests/datenbankregeln.test.ts` als Wächter.** Prisma erzeugt für jede künftige Migration
|
||
Löschbefehle gegen die handgeschriebenen Objekte. Der Test zählt sie namentlich auf, damit ein
|
||
übersehener `DROP` die Suite rot macht statt still die Mandantengrenze zu entfernen.
|
||
|
||
### Probleme
|
||
|
||
- **Die drei Blocker lagen alle an derselben Stelle: den Lesemodellen.** Der Suchfilter brachte
|
||
einen zweiten `OR`-Schlüssel mit und überschrieb die Kundenbedingung — ein Buchstabe im Suchfeld
|
||
genügte, um einem gebundenen Techniker alle Vorlagen aller Kunden zu zeigen. Historie und Aufbau
|
||
einer Version filterten gar nicht. Zu `queries/vorlagen.ts` gab es keinen Test; die Sicht in #008
|
||
abzusichern und in #009 nur den Service zu testen war der Fehler.
|
||
- **`Prisma.validator`-Objekte mit zwei gleichen Schlüsseln sind kein Typfehler.** Ein
|
||
zusammengebautes `where` mit `OR` aus zwei Quellen fällt weder ESLint noch `tsc` auf. Deshalb
|
||
liegt die Mandantenbedingung jetzt in `AND`, wo ein zweiter Filter sie nicht erreichen kann.
|
||
- **Prisma wollte sieben handgeschriebene Objekte löschen.** Die Migration musste von Hand
|
||
geschrieben werden; zusätzlich fehlten `@default(now())` bei den `@updatedAt`-Feldern, weshalb
|
||
Prisma bei jedem Lauf `DROP DEFAULT` vorschlug.
|
||
- **Zwei gleichzeitige Testläufe gegen `laufzettel_test`** ergaben 62 falsche Fehlschläge. Der
|
||
Review-Agent und ich haben uns die Daten weggetruncated; im Auftrag an den Agenten hatte ich die
|
||
eigene Datenbank nicht verlangt.
|
||
|
||
### Nächster Schritt
|
||
|
||
- #010 Vorlagen-Editor: Liste mit Suche und Filtern, Abschnitte und Schritte per Drag & Drop,
|
||
Vorschau, Veröffentlichen, Versionshistorie, Warnung bei unbekannten Platzhaltern.
|
||
|
||
---
|
||
|
||
## 2026-09-25 · Sitzung 6 · 08:20–10:30
|
||
|
||
**Issue:** #010 (M5)
|
||
**Version:** 0.1.0
|
||
|
||
### Erledigt
|
||
|
||
- Vorlagenliste mit Suche und Filtern, Vorlagenseite mit Versionsgeschichte, Kopfdaten, Duplizieren,
|
||
Archivieren, JSON-Ex- und -Import
|
||
- Vorlagen-Editor (Sub-Agent): Abschnitte, Schritte, Variablen — anlegen, bearbeiten, löschen,
|
||
sortieren mit Finger, Maus, Tastatur und Knöpfen; typabhängige Felder, Markdown-Hilfetexte,
|
||
Platzhalter-Warnung, Vorschau mit Beispielwerten
|
||
- `src/lib/platzhalter.ts` als einzige Quelle der eingebauten Platzhalter — #012 baut darauf auf
|
||
- Erste Playwright-Tests: der Editor wird durch die Oberfläche gefahren, einschließlich Sortieren
|
||
mit dem Finger bei 390 px
|
||
- Unabhängiger Review: zwei Blocker, sechs weitere Befunde, alle abgearbeitet
|
||
- **496 Tests** grün (vorher 428), dazu 5 E2E-Tests
|
||
|
||
### Entscheidungen
|
||
|
||
- **Oberflächen werden gegen den gebauten Stand abgenommen, nie gegen `pnpm dev`.** Der
|
||
Entwicklungsmodus hydriert in dieser Umgebung nicht; ein Screenshot sieht deshalb richtig aus,
|
||
während kein Knopf etwas tut. Das ändert die Arbeitsweise für alle folgenden Meilensteine und
|
||
steht in `CLAUDE.md` und `docs/architektur.md`.
|
||
- **Was nur mit angehängtem React gelingt, gehört in einen Playwright-Test.** Nicht erst in #018:
|
||
ohne ihn wäre „Sortieren funktioniert am Handy“ eine Behauptung geblieben.
|
||
- **Die Nummer eines Abschnitts kommt aus der Position und wird nie gespeichert.** Die
|
||
Formatdokumentation sagte bisher das Gegenteil; beim ersten Umsortieren wäre eine gespeicherte
|
||
Nummer falsch gewesen. Entfernt wird eine vorangestellte Nummer nur beim Trenner `·` und nur,
|
||
wenn dahinter keine Ziffer steht — „24 · 7 Bereitschaft“ bleibt.
|
||
- **Ein Knopf, der auf eine fehlende Seite zeigt, wird nicht verlinkt.** Galt für die Navigation
|
||
schon, jetzt auch für „Scannen“.
|
||
- **`format:pruefen` ist ein Tor.** Sonst formatiert `lint-staged` beim Commit nach, und im Commit
|
||
stehen Änderungen, die niemand gesehen hat.
|
||
|
||
### Probleme
|
||
|
||
- **Mein eigener Prüfaufbau hat mich getäuscht.** Ich habe Seiten im Browser angesehen, für gut
|
||
befunden und weitergemacht — in einem Entwicklungsmodus, in dem nichts anklickbar war. Aufgefallen
|
||
ist es erst, als ich zum ersten Mal wirklich geklickt habe. Zwei Stunden Suche nach der Ursache,
|
||
am Ende lag es am Werkzeug und nicht am Code; die Erkenntnis war die Zeit wert.
|
||
- **Ein Kommentar hat wieder eine Zusage gemacht, die kein Test hielt** („ein Test hält das fest“,
|
||
über der CSP). Dritter Meilenstein in Folge, in dem der Review genau diese Sorte Fehler findet.
|
||
Ein Kommentar, der eine Garantie behauptet, braucht den Test daneben — oder den Satz nicht.
|
||
- **Der Sub-Agent hat im Browser Dinge geprüft, die dort gar nicht prüfbar waren.** Mein Auftrag
|
||
hat ihn dorthin geschickt; die Lücke war meine.
|
||
- **Der Editor war bei einer einzigen Vorlage 16 986 px hoch.** Ich habe es im Screenshot gesehen,
|
||
nicht der Review.
|
||
|
||
### Nächster Schritt
|
||
|
||
- #011 Mitarbeiter, Abteilungen und Geräte: Listen mit Suche, Mitarbeiterseite mit Historie,
|
||
Abteilungen mit Ziel-OU und Standardgruppen, Geräte mit CSV-Import.
|