Commit Graph
16 Commits
Author SHA1 Message Date
Florian Wilde c38c988c3c feat: Anmelden mit Passkey
Passkeys (WebAuthn/FIDO2) als zweiter Anmeldeweg - auf der Anmeldeseite
und bei der erneuten Bestaetigung vor sensiblen Aktionen. Verwaltet unter
"Mein Konto".

Der Passkey ERSETZT den zweiten Faktor, statt zu ihm hinzuzukommen. Das
ist tragbar, weil er zwei Faktoren in sich vereint: Besitz des Geraets
und dessen Entsperrung. Damit das keine Annahme bleibt, wird die
Nutzerverifikation angefordert (userVerification: 'required') und
serverseitig nachgeprueft - eine Antwort ohne sie wird abgewiesen.

TOTP muss vorher eingerichtet sein. Ein Passkey haengt an einem Geraet;
waere er der einzige Weg hinein, koennte ein verlorenes Handy die Akte
schliessen.

Aufbau:
- Die Pruefung laeuft vollstaendig im Credentials-Provider "passkey",
  nicht in der Server Action davor. Ein "war schon geprueft" ueber die
  Grenze zu reichen waere die Stelle, an der sich Anmeldungen faelschen
  lassen.
- Challenges liegen serverseitig und werden per DELETE genau einmal
  verbraucht - die Datenbank entscheidet, wer sie bekommt.
- Bereichskennung und Herkunft kommen aus AUTH_URL, nicht aus einer
  zweiten Konfigurationsquelle.
- Ohne https wird der Knopf gar nicht erst angeboten.
- Rate-Limiting, Kontosperre und Audit-Log gelten unveraendert; die
  Anmeldung nennt den verwendeten Passkey namentlich.

Zwei Anmerkungen zur Migration:
- "prisma migrate diff" wollte zusaetzlich die Trigramm-Indizes aus
  prisma/sql/suche.sql loeschen, die Prisma nicht kennt. Die Zeilen sind
  entfernt und im Migrationskopf vermerkt - ein DROP haette die unscharfe
  Suche stillgelegt, ohne dass etwas fehlgeschlagen waere.
- "prisma format" hat das Schema durchgaengig neu ausgerichtet. Ohne
  Whitespace betrachtet sind es 88 hinzugefuegte Zeilen und keine
  geloeschte.

Geprueft: 21 Einheitstests, drei End-to-End-Tests gegen Chromiums
virtuellen Authenticator (einrichten, anmelden, bestaetigen, entfernen,
Geraet ohne Verifikation abgewiesen), beide Geraeteprofile. 496 Tests,
null Typfehler, Produktionsbau steht.

Nebenbei: Die Zahlen im README waren veraltet (24 statt 33 Modelle, 46
statt 79 Seiten). Sie stehen jetzt mit dem Befehl daneben, mit dem man
sie nachzaehlt.
2026-08-22 18:44:56 +02:00
Florian Wilde 0e7da42f27 docs: Bedienungsanleitung und Roadmap
Bis jetzt gab es Code und Betriebsdokumentation, aber nichts, was einem
beim Arbeiten hilft.

Die Anleitung ist kein Handbuch zum Durchlesen, sondern die zwoelf
Handgriffe, die im Alltag vorkommen - vom Behoerdenschreiben aufnehmen
bis zum Vertrag kuendigen. Dazu der Jahresrhythmus, die Berichte, was am
Handy anders ist, und ein Abschnitt "wenn etwas schiefgeht" mit den
Faellen, die wirklich passieren: Handy verloren, Code wird nicht
angenommen, Frist stimmt nicht, Bericht zeigt eine Differenz.

Ein eigener Abschnitt sagt, was die Anwendung NICHT tut. Wer sich auf
etwas verlaesst, das sie nicht leistet, faellt sonst genau dann darauf
herein, wenn es darauf ankommt.

Sie ist unter /anleitung auch in der Anwendung lesbar - dieselbe Datei,
kein zweiter Text, der auseinanderlaeuft. Wer im Heim steht und nicht
weiterweiss, hat sie auf dem Telefon dabei, und der Druckknopf legt sie
zu den Betreuungsunterlagen.

Dafuer ein schlichter Markdown-Baustein statt einer Bibliothek: die
Anwendung zeigt genau zwei solche Dokumente, beide schreibe ich selbst.
Rohes HTML wird nicht durchgereicht. Die Datenschutzseite benutzt ihn
jetzt ebenfalls, statt eine zweite Umsetzung zu pflegen.

Beide Dateien werden ueber outputFileTracingIncludes in den
Standalone-Build gezogen - der Pfad entsteht erst zur Laufzeit, die
Dateiverfolgung von Next.js kann ihn nicht erkennen. Ohne den Eintrag
waeren beide Seiten im Behaelter leer.

Die Roadmap sortiert die 44 Schema-Wuensche der Etappen nach einer
einzigen Frage zuerst: Kann daraus ein falscher Wert in einem Bericht
ans Gericht werden? Was das kann, steht in 1.1. Dazu ein Abschnitt
"bewusst nicht geplant" mit Begruendung - damit klar ist, worauf
niemand wartet.

Etappe: 8
Phase: Haertung
2026-08-22 10:51:28 +02:00
Florian Wilde b2ac13e128 feat: Versionierung mit Changelog, Version 1.0.0
Bis jetzt stand nirgends, welcher Stand laeuft. Beim Aktualisieren auf
einem Server ist das die erste Frage.

CHANGELOG.md nach Keep a Changelog, Versionsnummern nach SemVer. Der
Changelog sagt, was sich beim Arbeiten aendert - nicht, welche Datei
angefasst wurde. Dazu eine Tabelle, was Major, Minor und Patch fuer den
Betreiber praktisch bedeuten, und der Hinweis, dass sich eine
Datenbank-Migration nicht zurueckrollen laesst.

Die Version steht bewusst an zwei Stellen: package.json und
src/lib/version.ts. Ein Import der package.json waere im
Standalone-Build eine andere Datei als die des Projekts - die Zahl haenge
dann an der Laufzeit. Ein Test haelt beide zusammen, und
"npm run version" setzt sie gemeinsam samt Changelog-Abschnitt.

Committet und getaggt wird nicht automatisch. Ein Changelog-Eintrag, den
niemand gegengelesen hat, ist keinen Deut besser als keiner.

Sichtbar wird die Version unten in der Seitenleiste und unter
/api/gesundheit - dort mit Git-Stand, den der Docker-Build hineinreicht.
Auf der Anmeldeseite steht sie nicht: wer nicht angemeldet ist, muss
nicht wissen, welche Fassung hier laeuft.

Etappe: 8
Phase: Haertung
2026-08-22 10:41:33 +02:00
Florian Wilde 468202249a fix: Doppelte Wurzelroute entfernt, Produktionsbuild geprueft
Zwei Dateien loesten denselben Pfad "/" auf: src/app/page.tsx und
src/app/(app)/page.tsx. Next.js uebersetzt das noch, erzeugt beim
Standalone-Build aber ein unvollstaendiges Client-Manifest - und genau
dieses Verzeichnis kopiert das Docker-Abbild. Im Behaelter waere die
Anwendung damit erst beim Start umgefallen. Die Weiterleitung liegt
jetzt nur noch ausserhalb der Routengruppe.

Dazu vier ungenutzte Importe und Konstanten, die den Build blockierten,
und eine ESLint-Regel, die mit Unterstrich benannte Parameter durchgehen
laesst - _gemeldeterMimeTyp in der Dateiablage wird absichtlich nicht
benutzt, weil der vom Browser gemeldete MIME-Typ nichts beweist. Der
Name sagt das; ihn zu entfernen wuerde die Schnittstelle verschlechtern.

Geprueft am fertigen Standalone-Build, also genau so, wie der Behaelter
startet:
- strenge CSP greift (nonce plus strict-dynamic, kein unsafe-inline fuer
  Skripte) und blockiert dabei nichts Eigenes
- Anmeldung, alle Fachseiten, Benutzermenue und Schnellerfassung
  funktionieren
- kein Konsolenfehler, keine blockierte Anfrage
- keine einzige Anfrage an einen fremden Host

Etappe: 8
Phase: Haertung
2026-08-22 07:31:55 +02:00
Florian Wilde dccc34d669 test: Abnahmekriterien als Playwright-Tests, Anmeldungen im Protokoll
Zehn Tests, die genau die Zusagen aus dem Auftrag pruefen - an der
laufenden Anwendung, nicht an einzelnen Funktionen:

- Aus dem Bestellungsdatum entstehen die gesetzlichen Fristen, mit
  Rechtsgrundlage, und die Aufwandspauschale ist als Ausschlussfrist
  gekennzeichnet.
- Ein PDF laesst sich hochladen und in zwei Klicks einem Vorgang
  zuordnen.
- Eine Datei, die sich nur als PDF ausgibt, wird abgewiesen. Das ist der
  Angriff, gegen den die Pruefung an den Magic Bytes gebaut ist: HTML mit
  PDF-Endung und PDF-MIME-Typ, das spaeter als Seite ausgeliefert wuerde.
- Ein genehmigungspflichtiger Vorgang bietet die Stufe "Vollzogen" gar
  nicht erst an, solange kein Beschluss hinterlegt ist (§ 1858 BGB).
- Das Vermoegensverzeichnis kommt als echtes PDF heraus - geprueft wird
  die Signatur %PDF-, nicht nur der Dateiname.
- Der Vollexport verlangt eine frische Anmeldung.
- Am Handy: kein waagerechtes Scrollen der Seite, Bedienelemente
  mindestens 40 Pixel hoch, Schublade per Escape schliessbar.

Dazu ein echter Fund aus dem Protokolltest: Anmeldungen und
Abmeldungen tragen keine betreuteId - sie haengen am Konto, nicht an der
Akte. Die Protokollansicht filterte nur nach betreuteId und liess damit
ausgerechnet die Frage weg, wer wann auf die Akte zugegriffen hat. Jetzt
werden beide Arten von Eintraegen gezeigt.

34 Tests, je einmal am Schreibtisch und am Handy.

Etappe: 8
Phase: Haertung
2026-08-22 07:10:19 +02:00
Florian Wilde 5cc7f5c140 feat: Jahresbericht, Vollexport, Wissensbasis und Mitleser-Freigaben
Etappe 7. Die Ausgabeschicht - alles, was diese Anwendung nach aussen
abgibt.

Der Jahresbericht nach § 1863 BGB ist ein ENTWURF zum Ueberarbeiten,
kein fertiger Text. Er zieht sich die fuenf Gliederungspunkte aus
Kontaktprotokollen, Gesundheitsereignissen und Vorgangsstatus zusammen;
was davor abgegeben wird, entscheidet der Betreuer. Das sagt auch die
Oberflaeche. Ausgabe als PDF und als DOCX, damit sich weiterschreiben
laesst.

Vollexport als ZIP-Strom mit Gegendruck, damit auch grosse Bestaende
ohne Speicherausbruch durchgehen: Originaldateien nach Jahren sortiert,
alle Tabellen als entschluesseltes JSON, die erzeugten Berichte, ein
LIESMICH und SHA-256 je Datei. Damit laesst sich die Anwendung
verlassen - kein Datensilo.

Der Export ist eine sensible Aktion: nur BETREUER und ADMIN, nur nach
frischer Anmeldung, immer mit Audit-Eintrag. Ein Mitleser kommt nicht
heran.

37 Hinweiskarten in daten/hinweise.json, Zod-geprueft und ohne
Neustart pflegbar. Jede mit Rechtsgrundlage, Link und dem stehenden
Vermerk, dass sie keine Rechtsberatung ersetzt.

Modulweise Freigaben fuer Mitleser, mit ausdruecklicher Warnung bei
Gesundheit, Finanzen und Sozialhilfe. Rollen- und Freigabeaenderungen
erfordern eine frische Anmeldung und stehen im Audit-Log.

Nebenbei geloest: Webpack loest .tsx vor .ts auf, TypeScript und Vitest
umgekehrt. Ein Modul, das beide Endungen traegt, importiert sich unter
einem der beiden Werkzeuge selbst. Deshalb heissen die Bausteine jetzt
eindeutig (jahresbericht.entwurf.ts, .inhalt.tsx, .ausgabe.ts) und
werden ueber eine gemeinsame Fassade weitergereicht.

41 Tests.

Etappe: 7
Phase: Ausgabe
2026-08-22 06:22:44 +02:00
Florian Wilde b69a12da8f feat: Immobilie, Versicherungen und Heim mit COPD-Verlauf
Etappe 6. Das leerstehende Haus, die Vertraege und der
Gesundheitsverlauf - die drei Bereiche, in denen fuer diesen Fall das
meiste Geld und das meiste Risiko liegt.

Leerstands-Checkliste mit zehn Punkten, jeder mit Begruendung. Der
wichtigste: den Gebaeudeversicherer ueber den Leerstand informieren -
sonst droht Leistungsfreiheit, und zwar genau dann, wenn es brennt oder
eine Leitung platzt. Dazu Verkehrssicherungspflicht, Frostschutz zum
Oktober, Kontrollgaenge, Zaehlerstaende, Schluesselverzeichnis.

Der Kuendigungsrechner nennt den spaetesten ABSENDETAG, nicht den
Fristablauf: eine Kuendigung wirkt mit dem Zugang, nicht mit dem
Abschicken. Drei Tage Postlaufpuffer sind einkalkuliert.

Der Kuendigungs-Assistent beantwortet die Genehmigungsfrage nach
§ 1850 BGB nicht - er stellt sie und verlangt den Gang zum
Rechtspfleger. Der Versandnachweis ist Pflichtfeld: ohne ihn kommt die
Kuendigung nicht durch die Pruefung.

Leistungszuschlag nach § 43c SGB XI mit Stand 01.01.2024
(15/30/50/75 Prozent nach Verweildauer, erst ab Pflegegrad 2). Die
Oberflaeche zeigt, ab wann die naechste Stufe greift - das ist bares
Geld und wird regelmaessig uebersehen.

COPD-Schnellerfassung fuer Exazerbation, Sauerstoff, Sturz,
Krankenhaus, Hilfsmittel und Impfungen, mit STIKO-Plan fuer COPD.
Beschreibungen verschluesselt. Die Jahresauswertung ueber Exazerbationen
und Krankenhaustage wandert in den Jahresbericht und begruendet eine
Hoeherstufung.

74 Tests.

Etappe: 6
Phase: Substanz
2026-08-22 06:22:24 +02:00
Florian Wilde d35e24f830 feat: Sozialhilfe-Modul mit Nachforderungs-Tracker und Fristpruefung
Etappe 5. Bildet das laufende Verfahren beim Bezirk Oberfranken ab.

Rechtsbehelfsfrist: § 187 Abs. 1, § 188 Abs. 2 und 3 BGB, danach § 64
Abs. 3 SGG - hier verschieben Wochenende und Feiertag das Fristende auf
den naechsten Werktag. Das ist genau umgekehrt zu den Betreuerfristen,
wo bewusst nicht verschoben wird, und deshalb liegt die Feiertagsliste
getrennt.

Zwei bayerische Sonderfaelle, die haeufig falsch gemacht werden:
Mariae Himmelfahrt verschiebt NICHT (Art. 1 Abs. 1 S. 2 BayFTG - der
Feiertag gilt nur in ueberwiegend katholischen Gemeinden, Bayreuth ist
keine), und der Buss- und Bettag ist in Bayern seit 1995 kein Feiertag
mehr. Beide erzeugen stattdessen einen ausdruecklichen Hinweis, damit
man die Frist im Zweifel selbst nachrechnet. Der Rechenweg wird als
Schrittliste ausgegeben.

Heimkosten: Der Dashboard-Agent hatte einen fachlichen Widerspruch
gefunden - der einrichtungseinheitliche Eigenanteil auf der Heimrechnung
ist bereits um die Leistung der Pflegekasse gekuerzt. Wird sie zusaetzlich
als Deckung erfasst, entsteht eine Doppelzaehlung von ueber tausend Euro
im Monat. Das Modul rechnet jetzt ausdruecklich nach der Lesart
EIGENANTEIL_NETTO, fuehrt die Pflegekassenleistung nur nachrichtlich mit
und warnt, wenn sie doch belegt ist. Die Oberflaeche sagt auf einer
eigenen Karte, was hier gegen was gerechnet wird.

Die Schonvermoegens-Bewertung liefert Argumentationshilfen zum
Uebernehmen, keine Automatik - die Einordnung obliegt dem Traeger und
gegebenenfalls dem Sozialgericht. Das steht an jeder dieser Karten.

Alle jaehrlich veraenderlichen Betraege stehen an einer Stelle mit
Stand-Datum und Prueftermin. Die Bekleidungspauschale bewusst ohne Zahl:
sie ist regional und steht im Bescheid.

46 Tests.

Etappe: 5
Phase: Behoerde
2026-08-22 06:22:05 +02:00
Florian Wilde e7006c894e feat: Konten, Kassenbuch, Vermoegen und die beiden Pflicht-PDFs
Etappe 4. Aus diesen Daten entstehen das Vermoegensverzeichnis nach
§ 1835 BGB und die Rechnungslegung nach §§ 1859, 1865 BGB - beides geht
ans Betreuungsgericht, beides ohne dass eine Zahl abgetippt wird.

Betreuungsjahr n laeuft von bestellungAm + (n-1) Jahre bis
bestellungAm + n Jahre - 1 Tag. Bei Bestellung am 29. Februar endet Jahr
1 am 27.02. und Jahr 2 beginnt am 28.02.: lueckenlos und ohne
Ueberschneidung, per Test abgesichert.

Rechenprobe der Rechnungslegung: Der Anfangsbestand ist der juengste
Saldostand bis zum VORTAG des Zeitraumbeginns - ein Saldostand gilt als
Bestand am Tagesende, sonst zaehlte der erste Tag doppelt. Geht die
Probe nicht auf, wird die Differenz im PDF ausgewiesen und jeder
fehlende Saldostand namentlich benannt. Nie geglaettet: eine still
korrigierte Rechnungslegung ist gegenueber dem Gericht wertlos.

Posten ohne Wertstand zum Stichtag erscheinen als Luecke, nicht als
0,00 EUR - und Werte aus der Zukunft werden nie herangezogen. Konten
kommen ueber ihren Saldostand ins Verzeichnis; Vermoegensposten der Art
KONTO werden dort uebersprungen, sonst stuende das Geld doppelt drin.

Der CSV- und CAMT-Import raet KEINE Kategorien, sondern bucht alles auf
"Sonstige". Eine falsch geratene Kategorie faellt in der Jahresuebersicht
niemandem auf und verfaelscht den Bericht. Fremdwaehrung wird abgelehnt
statt umgerechnet. Der XML-Leser weist DOCTYPE ab (XXE).

68 Tests, darunter echte IBAN-Pruefziffern und die Rundung: es darf
nirgends ein Cent verschwinden.

Etappe: 4
Phase: Geld
2026-08-22 06:21:45 +02:00
Florian Wilde 56d4cca8c9 feat: Dokumentenablage, OCR, Volltextsuche und Belegmappe
Etappe 3. Dazu die geteilte Infrastruktur, auf der die Berichte der
folgenden Etappen aufsetzen: Speichertreiber und PDF-Grundgeruest.

Dateiablage hinter einer Schnittstelle, zwei Treiber - lokales
Verzeichnis fuer die Entwicklung, S3-kompatibel fuer den Betrieb. Der
Schluessel wird erzeugt, nie aus dem Dateinamen abgeleitet; ein
Pfadausbruch ueber den Namen ist damit ausgeschlossen.

Der Dateityp wird ausschliesslich an den Magic Bytes geprueft, und ein
PDF muss bei Offset 0 stehen - damit fallen auch Polyglot-Dateien durch,
die gleichzeitig gueltiges PDF und gueltiges HTML sind. Ausgeliefert
wird nur ueber kurzlebige signierte URLs, immer als Anhang und mit
nosniff. Die Route prueft Signatur, Anmeldung UND Zugriff auf die
betreuteId aus dem Schluessel; jeder Abruf steht im Audit-Log.

Beim Dateitreiber liegt die Datei zusaetzlich AES-256-GCM-verschluesselt.
Beim S3-Treiber geht das nicht, weil signierte URLs am Server vorbei
ausgeliefert werden - das ist in der Datei dokumentiert, nicht
stillschweigend anders gemacht.

@react-pdf/renderer konnte in diesem Abhaengigkeitsbaum keinen einzigen
Buchstaben setzen: die Unterabhaengigkeiten sind weitergewandert und
liefern fuer Standardschriften kein Schriftobjekt mehr.
src/lib/pdf/standardschrift.ts baut es aus den Metriken, die pdfkit
ohnehin mitbringt - kein neues Paket, kein Netz - und haengt sich nur
ein, wenn die Aufloesung tatsaechlich kaputt ist.

Fristerkennung im OCR-Text ("innerhalb von vier Wochen", "binnen eines
Monats") wird immer nur VORGESCHLAGEN, nie automatisch angelegt. Die
Textstelle steht daneben, damit man nachprueft, bevor man uebernimmt.
Gerechnet wird auf lokaler Mittagszeit: in UTC verschoebe die Sommerzeit
"vier Wochen ab 12.03." um einen Tag.

57 Tests.

Etappe: 3
Phase: Papier
2026-08-22 06:21:27 +02:00
Florian Wilde 3833a9dd8f docs: README, Datenschutztexte, Dockerfile und Sicherungsskripte
Damit ist Etappe 1 abgeschlossen und betriebsfertig dokumentiert.

README mit beiden Betriebswegen. Docker Compose auf eigenem Server ist
der Hauptweg; Vercel und Neon sind vollstaendig beschrieben, aber mit
einer klaren Begruendung, warum ich davon abrate: beide Anbieter sind
US-Unternehmen und unterliegen auch bei EU-Region potenziell dem
CLOUD-Act. Bei Gesundheitsdaten einer nicht entscheidungsfaehigen Person
ist das eine Abwaegung, die man bewusst treffen muss.

Reverse-Proxy-Beispiele fuer Caddy, nginx und Traefik - der Proxy selbst
ist nicht Teil des Compose-Setups, weil er auf dem Zielserver schon
steht. Die Datenbank hat keine Portfreigabe.

Sicherung: pg_dump schreibt direkt in die Verschluesselung, der Klartext
landet nie auf der Platte. Danach eine Selbstpruefung - laesst sich die
Datei nicht wieder entschluesseln, endet der Lauf mit Fehler statt mit
einem stillen Scheinerfolg. Der jeweils erste Stand eines Monats bleibt
dauerhaft stehen.

Die README sagt ausdruecklich, was das Backup NICHT enthaelt: den
Schluessel ENC_KEY_V1 und die Dateien im Objektspeicher. Dazu eine
Anleitung, die Wiederherstellung einmal im Jahr zu ueben - mit dem
letzten Schritt, in der Anwendung nachzusehen, ob die Diagnosen im
Klartext erscheinen. Erst dann stimmt nicht nur die Datenbank, sondern
auch der Schluessel.

Verarbeitungsverzeichnis und Datenschutzhinweise als ausfuellbare
Textbausteine. Die Hinweise sind in einfacher Sprache geschrieben - sie
sollen der betreuten Person vorgelesen werden koennen. Die Seite
/datenschutz liest denselben Text aus docs/, damit es nicht zwei
Fassungen gibt, die auseinanderlaufen.

Dockerfile in drei Stufen mit Tesseract, deutschen Sprachdaten und
poppler-utils. Zeitzone Europe/Berlin ist gesetzt - ohne sie rechnet der
Server in UTC und verschiebt Fristen um einen Tag.

Etappe: 1
Phase: Fundament
2026-08-22 02:35:08 +02:00
Florian Wilde 6b4df4c9cb feat: Dashboard Heute, Kontakte und Kontaktprotokoll
Das Dashboard beantwortet die Frage, was heute ansteht - nicht, welche
Daten es gibt. Jede Kachel endet in einer Handlung: Fristen-Ampel,
wartet auf andere (mit Tagen seit dem letzten Kontakt), offene
Nachforderungen des Bezirks mit Fortschrittsbalken, ungepruefter
Posteingang, unverbuchte Belege, Kennzahlen.

Zwoelf Abfragen in einem Promise.all, kein N+1. Cent-Betraege werden
beim Verlassen der Serverseite gewandelt.

Kacheln zu Modulen, die ein Mitleser nicht freigegeben bekommen hat,
entfallen ganz - sie zeigen nicht "0", denn das waere eine Aussage ueber
Daten, die er nicht sehen darf.

Kontaktprotokoll als Schnellerfassung: Datum vorbelegt, Art als grosse
Knopfreihe, Dauer als Schnellwahl, ein Freitextfeld. Alles andere
eingeklappt. Ziel sind unter 20 Sekunden am Handy.

Das Haekchen "persoenlicher Kontakt zur betreuten Person" ist bewusst
NICHT vorbelegt - es wandert in den Jahresbericht nach § 1863 BGB, und
ein versehentlich gesetztes Haekchen waere dort eine falsche Angabe
gegenueber dem Gericht.

Aus demselben Grund gilt der Textbaustein "regelmaessiger persoenlicher
Kontakt" erst ab persoenlichem Kontakt in mindestens 75 Prozent der
Monate. Sonst schreibt die Auswertung ausdruecklich, dass kein
persoenlicher Kontakt dokumentiert ist. Ein geschoenter Bericht hilft
niemandem.

Protokolldaten werden als 12:00 UTC gespeichert und durchgaengig in UTC
gerechnet, sonst kippt die Zeitzone einzelne Besuchstage.

51 Tests.

Etappe: 2
Phase: Alltag
2026-08-22 02:26:38 +02:00
Florian Wilde 1217725076 feat: Vorgaenge als Fallakte mit Genehmigungskette und Vorlagen
Jeder Vorgang ist eine kleine Akte: Kopfdaten, Aufgabenliste zum
Abhaken, Genehmigungsstufen und ein Zeitstrahl, der Dokumente,
Kontaktprotokolle, Fristen, Buchungen und Statuswechsel zusammenfuehrt.

Die abnahmerelevante Regel: Ein genehmigungspflichtiger Vorgang laesst
sich nicht auf VOLLZOGEN setzen, solange kein Beschlussdokument
hinterlegt ist. Geprueft wird in darfStatusWechseln() UND in der Server
Action - nicht nur im UI, wo das Auswahlfeld die Stufe ohnehin gar nicht
erst anbietet. Hintergrund ist § 1858 BGB: die Genehmigung muss vor dem
Rechtsgeschaeft vorliegen, nicht danach.

Neun Vorlagen mit durchdachter Aufgabenreihenfolge. Bei Wohnungs-
aufloesung und Hausverkauf erzwingen sie die Kette Mitteilung ->
Genehmigung -> Rechtsgeschaeft; ein Test prueft das.

Kanban ohne Drag-and-Drop: Statuswechsel laufen ueber Auswahlfeld und
Knopf. Am Handy ist das verlaesslicher, und am Schreibtisch kostet es
einen Klick mehr statt einer verlorenen Karte.

Zeitzonen: alsTag() bildet den oertlichen Kalendertag auf Mitternacht
UTC ab. Ohne das rutscht in Deutschland jede Faelligkeit einen Tag
zurueck.

33 Tests.

Etappe: 2
Phase: Alltag
2026-08-22 02:24:16 +02:00
Florian Wilde 63352272b8 fix: Navigationsstruktur aus der Client-Komponente herausloesen
Das Server-Layout filtert die Navigation danach, welche Module der
angemeldete Benutzer sehen darf. Es importierte NAV_STRUKTUR aber aus
einer Datei mit "use client" - dort kommt auf dem Server kein Wert an,
sondern nur ein Verweis. NAV_STRUKTUR.filter war entsprechend keine
Funktion, und jede Fachseite lief in einen Serverfehler.

Zweiter Fehler in derselben Zeile: die Eintraege trugen die Icons als
React-Komponenten. Die lassen sich nicht ueber die Server/Client-Grenze
reichen.

Beides behoben, indem die Struktur nach src/lib/navigation-struktur.ts
wandert und dort nur noch ein Kuerzel je Icon steht. Die Client-
Komponente uebersetzt das Kuerzel. Die Wiederausfuhr aus navigation.tsx
wurde bewusst weggelassen, sonst stellt sich dieselbe Falle wieder auf.

Dazu scripts/screenshots.mjs: meldet sich mit dem Entwicklungszugang an
und fotografiert Seiten am Schreibtisch und am Handy, hell und dunkel.

Etappe: 1
Phase: Fundament
2026-08-22 02:22:36 +02:00
Florian Wilde 22b3a9b9e9 feat: Stammdaten, Ersteinrichtung, Zweitfaktor und Protokollansicht
Damit ist Etappe 1 fachlich vollstaendig.

Ersteinrichtung in drei Schritten, bewusst ausserhalb des
Anwendungsrahmens: der setzt Benutzer, zweiten Faktor UND eine betreute
Person voraus - beim allerersten Start gibt es nichts davon. Die Aktion
fuer den ersten Zugang sperrt sich selbst, sobald ein Benutzer existiert.

Zweiter Faktor: Das Geheimnis wird zuerst nur abgelegt, ohne
totpAktiviertAm. Erst ein echter Code aus der App schaltet ihn scharf.
Wer sich beim Abtippen vertut oder die falsche App nimmt, wuerde sich
sonst dauerhaft aussperren. Erst danach gibt es die
Wiederherstellungscodes - einmal angezeigt, danach nur noch als Hash.

Betreuungsjahr (src/lib/stammdaten/betreuungsjahr.ts): laeuft ab dem Tag
der Bestellung, nicht ab dem Kalenderjahr. Schrittweise vorwaerts
gerechnet statt ueber eine Differenz, damit Schaltjahre und der
29. Februar von selbst stimmen. Daran haengen Jahresbericht,
Rechnungslegung und die Ausschlussfrist der Aufwandspauschale.

Aendert sich das Bestellungsdatum, werden die Pflichtfristen neu
abgeleitet - ueber einen dynamischen Import, damit die Stammdaten auch
dann speicherbar bleiben, wenn das Fristenmodul fehlt.

Protokollansicht zeigt je Eintrag die geaenderten Felder als alt -> neu.
Die Aenderungen liegen verschluesselt, weil dort Diagnosen und
Kontostaende stehen koennen.

Seed legt bewusst keine echten Personendaten an, nur den ersten Zugang.
Mit SEED_DEMO=true kommt ein vollstaendiger Beispielfall dazu - Erika
Musterfrau, COPD GOLD III, Pflegegrad 3, Heimeinzug Januar 2026,
leerstehendes Haus, zwei Versicherungen, laufender Antrag beim Bezirk
mit acht Nachforderungen. Nur ausserhalb der Produktion.

Docker Compose ohne Reverse Proxy - der ist auf dem Zielserver
vorhanden. Die Datenbank hat keine Portfreigabe und ist nur ueber das
interne Netz erreichbar; die Anwendung horcht auf 127.0.0.1.

Etappe: 1
Phase: Fundament
2026-08-22 02:16:22 +02:00
Florian Wilde dcd3a8ce71 feat: Oberflaechenbausteine, Anwendungsrahmen und Konventionen
Basisbausteine im shadcn/ui-Stil, aber mit drei bewussten Abweichungen:

- Bedienelemente sind 44px hoch statt 36px. Ich bediene die App oft am
  Handy im Heim oder auf dem Amt; kleiner ist dort nicht treffsicher.
- Auswahlfelder benutzen das native <select>. Android und iOS oeffnen
  dafuer ihre eigene Liste - schneller als jeder Nachbau, und mit
  Screenreader und Tastatur funktioniert es ohne Zutun.
- Eingabefelder haben mindestens 16px Schrift, sonst zoomt iOS-Safari
  beim Hineintippen das Formular heran.

<Feld> verdrahtet Beschriftung, Erklaerung und Fehler ueber
aria-describedby. Der Erklaerungstext ist kein Beiwerk: die Anwendung
benutzt Begriffe aus dem Betreuungsrecht, die niemand nachschlagen soll.

<Rechtshinweis> ist der stehende Vermerk, dass hier keine Rechtsberatung
stattfindet. Er steht an jeder Stelle mit Paragraphenbezug.

Der Anwendungsrahmen fuehrt drei Bedingungen zusammen, ohne die niemand
an Fachdaten kommt: angemeldet, zweiter Faktor eingerichtet, mindestens
eine betreute Person zugeordnet. Die Navigation zeigt Zaehler fuer
ueberfaellige Fristen und neuen Posteingang - die Frage "wo muss ich
hin?" wird schon im Menue beantwortet.

Das Farbschema wird per Inline-Skript mit CSP-Nonce gesetzt, bevor die
Seite gezeichnet wird - sonst blitzt beim Laden das helle Thema auf.

docs/KONVENTIONEN.md haelt fest, was in allen weiteren Etappen gilt:
Verzeichnisaufbau, Cent-Betraege, Umgang mit verschluesselten Feldern,
Zugriffspruefung, Audit-Pflicht, Ampelfarben, Commit-Trailer.

Etappe: 1
Phase: Fundament
2026-08-22 01:54:56 +02:00