# Devlog Entwicklungstagebuch mit Zeiterfassung. Jeder Arbeitsabschnitt bekommt einen Eintrag: wann, wie lange, was entstanden ist, und was dabei schiefging. Die aufgewendete Zeit wandert nach jedem Abschnitt in die Spalte „tatsächlich" der [Aufwandsschätzung](AUFWANDSSCHAETZUNG.md). ## Zeitübersicht | Phase | Sitzungszeit | Sitzungen | abgeschlossen | |---|---:|---:|---| | Phase 0 — Fundament | 1:00 h | 1 | 03.09.2026 | | Phase 1 — Stammdaten | 1:15 h | 1 | 03.09.2026 | | Phase 1 — Nachtrag Geräteformular | 0:50 h | 1 | 03.09.2026 | | Phase 2 — Wartung und Prüfung | 2:25 h | 1 | 03.09.2026 | | Phase 3 — Einsätze und Übungen | 2:10 h | 1 | 03.09.2026 | | Phase 4 — Kiosk-PWA im Gerätehaus | 3:00 h | 1 | 03.09.2026 | | Phase 5 — Atemschutz nach FwDV 7 | 2:40 h | 1 | 03.09.2026 | | Phase 6 — Kartenmodule | 2:50 h | 1 | 04.09.2026 | | Phase 7 — Benachrichtigungen und Termine | 3:10 h | 1 | 04.09.2026 | | Phase 8 — Lokale KI | 3:40 h | 1 | 04.09.2026 | | Aufräumarbeiten (Issues #10–#12) | 1:20 h | 1 | 04.09.2026 | | **Summe** | **24:20 h** | **11** | | --- ## 2026-09-04 · Aufräumarbeiten — Issues #10 bis #12 **11:15 – 12:35 · 1:20 h** ### Was entstanden ist Drei Punkte, die seit mehreren Phasen im Abschlussbericht standen und in jeder Phase Zeit gekostet haben. Diesmal als Issues angelegt und abgearbeitet. | Issue | Kern | |---|---| | #11 | ESLint hatte keine Konfiguration — `npm run lint` prüfte nichts | | #12 | `
` in einem `

` auf der Einsatzseite, seit Phase 3 | | #10 | Die E2E-Tests schrieben in die Beispielwehr | ### Entscheidungen **ESLint zuerst.** Er findet die anderen beiden — und tatsächlich hat er gleich zwei Fehler gemeldet, die niemand gesucht hatte: eine Fehlerfläche auf der Karte, die nie etwas anzeigen konnte, und zwei `useMemo` in der Lagekarte, die bei jedem Rendern neu rechneten. **Keine Formatierungsregeln.** Die macht Prettier. Zwei Werkzeuge, die sich über Anführungszeichen streiten, lenken von den Regeln ab, auf die es ankommt. **Auch keine typgestützten Regeln.** `no-floating-promises` wäre für diesen Stack die wertvollste Regel überhaupt — sie braucht aber den Compiler bei jedem Lauf, und dann dauert der Lauf Minuten statt Sekunden. Bei einem Werkzeug, das vor jedem Commit laufen soll, ist das der falsche Tausch. **Das Badge wurde ein ``, nicht der Absatz ein `

`.** Die Meldung kam von einer Stelle, aber die Ursache saß im Bauteil: ein Abzeichen ist Fließtext-Inhalt und gehört in ein Inline-Element. So ist auch jede künftige Verwendung in einem Absatz richtig, und die Darstellung ändert sich nicht — `inline-flex` verhält sich in beiden gleich. **Eine zweite Wehr statt Aufräumen nach dem Lauf.** Ein abgebrochener Lauf lässt beim Aufräumen einen Bestand zurück, den niemand erwartet. Ein vollständiger Neuaufbau ist der einzige Zustand, den man kennt — und der Seed kann ohnehin schon alles, er brauchte nur eine Kennung aus der Umgebung. **Ein Riegel gegen den eigenen Fehler.** `npm run db:test-reset` bricht ab, wenn die Testkennung mit der der Beispielwehr übereinstimmt. Ein Skript, das eine ganze Wehr löscht, braucht eine Stelle, an der es sich weigert. ### Wo es gehakt hat | Problem | Ursache | Lösung | |---|---|---| | Zwei Tests fielen nach der Umstellung durch — und das war richtig so | Sie hatten die Verschmutzung gebraucht. `getByText("B 3 Wohnhausbrand", { exact: true })` traf bisher einen Einsatz aus einem früheren Testlauf, weil der kein Objekt hatte; der Einsatz aus dem Seed hat eines und steht in derselben Zelle | Anker am Zellenanfang statt Prüfung auf Gleichheit | | Der Kiosk-Test schrieb weiter in die Beispielwehr | `const WEHR = "ff-musterhausen"` stand fest im Test. Das Terminal merkt sich die Wehr und schreibt Mängel und Fahrten genau dorthin | Kennung aus `wehr.ts`, wie überall sonst | | Ein doppelt deklarierter Bezeichner rutschte durch den Typecheck | `tests/e2e` war in der `tsconfig.json` ausgeschlossen | Ausnahme entfernt. Sie hat nichts gebracht: die Dateien übersetzen sauber | | Die Anmeldung im Bildschirmfoto-Skript lief ins Leere | Bekannt aus Phase 7 — bei kaltem `.next` ist die Seite noch nicht hydriert | War schon behoben; hier nur wieder aufgetreten und bestätigt | ### Abweichung von der Vorgabe **Issues angelegt**, obwohl CLAUDE.md das ausdrücklich verbietet („Niemals ein Issue anlegen"). Auf ausdrückliche Anweisung im Chat — die Regel stammt vom Auftraggeber, und er hat sie für diesen Vorgang aufgehoben. ### Nachweise - `npx tsc --noEmit` — ohne Ausgabe, jetzt **einschließlich** `tests/e2e` - `npm run lint` — ohne Beanstandung - `npm run test` — 495 Tests grün - `npx playwright test` — grün gegen die Testwehr - Die Beispielwehr steht danach unverändert auf dem Seed-Stand: 49 Geräte, 8 Einsätze, 1 Atemschutzüberwachung, 19 Termine, 4 Wiki-Seiten --- ## 2026-09-04 · Phase 8 — Lokale KI **07:30 – 11:10 · 3:40 h · Version 0.9.0** ### Was entstanden ist Ein Wiki, das man befragen kann, ein Diktat, ein Berichtsassistent, eine Belegerfassung — und zwei Protokolle, die belegen, was davon eine Maschine getan hat. | Bereich | Dateien | |---|---| | Rechnen | `lib/ki/zerlegen.ts`, `beleg-lesen.ts`, `anweisungen.ts`, `wiki.ts`, `vorgang.ts` | | Kanäle | `lib/integrations/llm.ts` (um Einbettungen und Bilder erweitert), `whisper.ts` | | Server | `server/routers/ki/wiki.ts`, `assistent.ts` | | Oberfläche | `app/(app)/wiki/`, `belege/`, `ki-protokoll/`, `protokoll/`; `components/ki/` | | Hintergrund | `worker/ki.ts`, zwei neue Auftragsarten | | Betrieb | `docker/postgres/Dockerfile` (PostGIS **und** pgvector), Ollama und faster-whisper im Profil `ki` | | Tests | 73 Unit, 14 E2E je Browser | ### Entscheidungen **Nichts Maschinelles gelangt ungeprüft in einen Nachweis.** Das ist die Regel aus Phase-8.md, und sie bestimmt den ganzen Aufbau: jede KI-Ausgabe ist zuerst ein eigener Datensatz und nicht ein Feld im Zieldokument. Es gibt keine Funktion, die eine Ausgabe erzeugt und gleich übernimmt — wer das umgehen will, muss Code schreiben, und das fällt in einer Durchsicht auf. Die Datenbank hält gegen: Eingabe, Ausgabe, Modell und Art sind nach dem Anlegen unveränderlich, gelöscht wird nicht, und über einen Vorschlag wird genau einmal entschieden. Eine Absicht im Code ist eine Absicht; ein Trigger ist eine Zusicherung. **Kein Rückfall auf einen Anbieter.** Fehlt der eigene Endpunkt, fehlt die Funktion. Ein Einsatzbericht spricht über Menschen in ihrer schlechtesten Stunde — solche Texte gehören nirgendwo anders hin als ins Gerätehaus. Und ein Rückfall, der „nur im Notfall" greift, ist genau der Fall, in dem niemand hinsieht. **Ohne Modell bleibt alles andere benutzbar.** Das war der größte Einzelposten der Phase und steht in keiner Aufwandsschätzung. Vier Hinweise an vier Stellen, eine zweite Suche, die immer funktioniert, und vierzehn E2E-Tests, die genau diesen Zustand absichern — der Testlauf hat keinen Ollama daneben, und das ist Absicht. **Drei Modelle statt einem.** Text, Einbettungen und Bilder sind einzeln konfigurierbar, weil ein Rechner ohne Grafikkarte das Einbettungsmodell noch flott schafft, das Textmodell zäh und das Vision-Modell praktisch nicht. Eine Wehr kann so ihr Wiki durchsuchbar machen, ohne Berichte schreiben zu lassen. **Zerlegt wird an den Überschriften, nicht nach Zeichenzahl.** Weil die Fundstelle später unter der Antwort steht, und eine Fundstelle soll ein Kapitel sein. Dazu kommt die Überschriftenkette in den Einbettungstext: ein Abschnitt über „Rückzugsdruck" ohne das Wort „Atemschutz" darüber findet sich sonst nicht, wenn jemand nach Atemschutz fragt. **Markdown wird ohne HTML gerendert.** Der Text kommt von Anwendern. Jeder Weg über `dangerouslySetInnerHTML` wäre ein Weg für ein `