Neuer Ordner ios/ mit dem Geruest: Netzwerkschicht, Modelle, Offline-Ablage,
Texte in Deutsch und Englisch, Navigation, der komplette Anmeldeweg (Server
waehlen, anmelden, zweiter Faktor, Pflicht-Passwortwechsel) und als erste
Fachansicht die Kontenliste.
Die Anmeldung laeuft einmal ueber das Sitzungs-Cookie, holt sich dabei eine
App-Marke und legt sie in den Schluesselbund. Danach traegt nur noch der
Header. Kein Passwort auf dem Geraet, kein Ablauf nach 30 Tagen.
Der Code ist nie uebersetzt worden - hier gibt es kein Xcode. Geprueft ist
dafuer jedes Modellfeld gegen echte Antworten des laufenden Backends; dabei
kam heraus, dass created_at ohne Zeitzone und mit Mikrosekunden kommt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die API kannte zwei Zustaende: angemeldet per Sitzungs-Cookie, oder eine enge
Kurzbefehl-Marke, die nur erfassen darf. Eine native App passt in keinen davon
- sie kann kein Cookie-Formular ausfuellen und soll das Passwort nicht auf dem
Geraet halten.
Neu ist die Reichweite "app": Sie gilt im Header X-Certain-Token wie eine
Anmeldung, laeuft nicht nach 30 Tagen ab und laesst sich einzeln widerrufen.
Die enge Marke bleibt eng - das ist der Punkt, an dem die Tests am genauesten
hinsehen. Und eine App-Marke darf keine weitere ausstellen: Sonst stellte sich
ein Dieb Ersatzschluessel aus und das Widerrufen liefe ins Leere.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mit Notiz und Tags ist die Buchungsliste breiter als der Bildschirm geworden.
Die Knopfspalte scrollte damit einfach aus dem Bild - und weil sie in einer
gespeicherten Spaltenreihenfolge auch noch mittendrin stand, war sie gar
nicht mehr zu finden. "fixed" hiess bisher nur "nicht verschiebbar", nicht
"bleibt stehen".
Angeheftete Spalten (neu: pin) kleben jetzt am rechten Rand und ruecken
immer ans Ende, egal was gespeichert ist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisher musste man dafuer die ganze Buchung aufmachen. Jetzt steht in der
Notiz-Spalte ein Feld: hineinschreiben, Enter oder wegklicken - fertig.
Escape nimmt die Aenderung zurueck, leeren loescht die Notiz.
Und sie steht nicht mehr doppelt da. Die dritte Zeile unter der Buchung
zeigte dieselbe Notiz wie die neue Spalte; sie faellt weg, der
Verwendungszweck darueber bleibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
source-map-js 1.2.1 -> 1.2.2 (GHSA-68fv-2mgg-jv7q). Eine reine
Build-Abhaengigkeit ueber Vite und PostCSS, sie landet nie im Browser.
Die beiden verbleibenden Meldungen betreffen react-router und waeren nur mit
dem Sprung auf Version 7 zu beheben. Beide greifen hier nicht: Der offene
Redirect braucht ein Navigationsziel aus Nutzereingaben, es gibt aber nur
feste Pfade; die zweite betrifft SSR-Hydration, und die App ist ein reiner
SPA ohne SSR.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Notiz stand nur im Bearbeiten-Dialog, obwohl man sie schreibt, um sie
spaeter wiederzufinden. Jetzt hat sie eine eigene Spalte mit eigenem Filter;
der ganze Text steht im Tooltip. Das Feld im Dialog ist wieder einzeilig und
laesst sich bei Bedarf aufziehen.
Statt einer Einfachauswahl gibt es drei Haken: Ausgaben, Einnahmen,
Uebertraege. Man will oft zwei Arten gleichzeitig sehen und eine dritte
weglassen.
Und "nur Einnahmen" zeigt keine Uebertraege mehr. Eine verknuepfte Buchung ist
weder Ausgabe noch Einnahme, sie verschiebt nur Geld - nach ihrem rohen Typ
gefiltert stand sie trotzdem unter den Einnahmen, und weil der Server nur eine
Seite des Paares lieferte, sogar einzeln statt als eine Zeile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Kategorie beantwortet "wofuer war das Geld" und gilt genau einmal. Alles,
was quer dazu liegt – "Urlaub Island", "absetzbar", "zurueckfordern" –, hatte
bisher keinen Platz, weil eine Buchung davon mehrere auf einmal sein kann.
Tags werden auf einer eigenen Seite angelegt, jeder mit seiner Farbe. Gesetzt
werden sie an der Buchung: direkt in der Liste oder beim Bearbeiten. Die
Spalte laesst sich filtern, auch nach mehreren Tags gleichzeitig.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das feste Zielkonto eines Transfer-Schlagworts behielt beim Wiederherstellen
die alte Konto-Nummer. Die Nummern werden dabei aber neu vergeben, also lief
es ins Leere – und zwar so, dass die GANZE Wiederherstellung mit einem
Fremdschluessel-Fehler abbrach, nicht nur diese eine Zeile. Wer je ein
Zielkonto gewaehlt hatte, kam an seine Sicherung nicht mehr heran.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwischen zwei Konten liess sich kein zweiter Uebertrag ueber denselben
Betrag buchen. Die Begruendung stimmte fuer den haeufigen Fall: Beide
Seiten kommen oft getrennt herein, einmal von der Bank, einmal aus der
Datei – wer beide einzeln verknuepft, verschiebt das Geld zweimal.
Nur ist sie nicht immer richtig. Revolut-Aufrundungen sind reihenweise
CHF 1.00 zwischen demselben Paar in denselben Tagen, und die sind alle
echt. Als einziger Ausweg blieb, den vorhandenen Uebertrag zu loeschen.
Jetzt wird gefragt statt verboten: dieselbe Warnung, aber ueber
"trotzdem" zu uebergehen – der Weg, den alle anderen Pruefungen hier
schon nehmen. Entscheiden muss das der Nutzer; die Regel kann es nicht
wissen.
Dabei fiel auf, dass die Rueckfrage HINTER dem Fenster lag, das sie
aufgeworfen hat: Modal rendert per Portal an den Koerper und liegt damit
spaeter im Baum, bei gleicher Ebene gewinnt der spaetere. Eine
Rueckfrage gehoert ueber das, wonach sie fragt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Liste sagt ausdruecklich, dass sie nichts entscheidet: Drei
Aufrundungen ueber 1.00 am selben Tag sind echt, drei gleiche Einkaeufe
koennen es auch sein – das weiss nur der Nutzer. Dann darf sie aber auch
nicht zu einer Entscheidung draengen, und genau das tat sie: Der einzige
Knopf war der Papierkorb. Wer die Meldung loswerden wollte, musste eine
richtige Buchung wegwerfen.
Jetzt steht neben jeder Gruppe "Alle behalten". An den Buchungen aendert
sich dabei nichts; es ist ein Haken an der Frage.
Der Haken gilt dem, was man gesehen hat – nicht allem, was noch kommt:
Eine Gruppe faellt erst weg, wenn ALLE ihre Zeilen ihn tragen. Kommt
spaeter eine dritte gleich aussehende Buchung dazu, steht die Frage
wieder da, und zwar fuer alle drei. Ein Test haelt das fest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das native Dropdown fiel neben der Kategorie-Auswahl auseinander: anderer
Stil, kein Suchfeld, kein Symbol, und die Vertraege standen in
Anlagereihenfolge. Bei zwanzig Abos sucht man einen bestimmten, und die
Reihenfolge der Eingabe kennt niemand.
Jetzt dieselbe Auswahl wie bei der Kategorie. CategorySelect bekommt dafuer
zwei kleine Zugaenge statt eines zweiten Dropdowns: ein eigenes Symbol je
Eintrag (renderMarke) und eine Gruppe ohne Namen, die ohne Kopfzeile
auskommt – die Vertraege haben keine Gruppen. Alles andere, von der
Suche bis zum Schliessen beim Scrollen, bleibt eine Mechanik.
Das Logo schlaegt dabei das Symbol: Netflix erkennt man am roten N, nicht
am Wort. Die Fallunterscheidung dafuer stand schon in der Abo-Liste und
ist jetzt eine Komponente, die sich beide teilen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Erkennung lief seit Stufe 3, aber nur automatisch: Was sie nicht
sicher traf, blieb liegen, und sehen konnte man davon nichts. Die
Endpunkte gab es, die Oberflaeche kannte keinen davon.
Jetzt steht in der Buchungsliste eine Spalte "Abo" – dieselbe Form wie
die Kategorie daneben: Die Auswahl zeigt, zu welchem Vertrag die Zahlung
gehoert, aendert es, und leer loest die Zuordnung. Uebertraege bleiben
aussen vor; sie verschieben nur Geld.
Und sie lernt. Musste man von Hand ran, hat der Erkennungstext nicht
gepasst – also wird der Empfaenger dieser Buchung der neue. Beim
naechsten Mal faellt dieselbe Zahlung von selbst zu, statt dass man
jeden Monat dasselbe klickt. Der Vertragsname bleibt als zweite Chance:
_passt_text prueft beide, nicht entweder/oder, sonst verlaere man beim
Lernen, was vorher schon getroffen hat.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Kontostands-Karte zeigte die Saeule 3a wie jedes andere Konto: ein
Name, ein Betrag. Dass man an das Geld nicht herankommt, stand nur in
der Summe darunter – und wer den Betrag liest, rechnet ihn im Kopf doch
mit. Die Zeile traegt jetzt dieselbe Marke wie auf der Kontenseite.
Dabei ist ein Fehler aufgefallen, den ich selbst eingebaut hatte: Die
Zeile "zusaetzlich gebunden" stand im Markup HINTER der Summe. Die
klebt per position:sticky am unteren Rand des Rollbereichs – alles
dahinter liegt darunter und ist praktisch nie zu sehen. Summe und
gebundener Betrag stehen jetzt gemeinsam im angehefteten Fuss.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Dinge, die zusammengehoeren.
1. Uebertraege sind als solche lesbar.
Stand nur eine Seite in der Liste – weil nach einem Konto gefiltert wird
oder die andere nicht mitgeladen war – trug die Zeile den Rohtext der
Bank als Ueberschrift. "To CHF MB:8f33c0aa-9c92-456b-a6fe-b64a858a019c"
sah aus wie eine Zahlung an jemanden; die Verknuepfung stand klein
darunter. Jetzt steht oben, zwischen WELCHEN Konten das Geld lief, in
beiden Faellen gleich, und daneben eine Marke "Transfer". Ein Uebertrag
ist weder Ausgabe noch Einnahme – das gehoert in die erste Zeile, nicht
in eine Fussnote.
2. Notiz je Buchung.
Das Feld gab es im Modell seit jeher, die Oberflaeche bot es nie an. Die
Kategorie sagt, in welche Schublade etwas gehoert – nicht, WAS es war.
"Amazon" kann Einrichtung sein, Werkzeug oder ein Geschenk, und in drei
Monaten weiss das niemand mehr. Die Notiz steht in der Liste und ist
mitdurchsuchbar: Wer sie schreibt, will spaeter danach suchen.
3. "Ignorieren" ist keine Einbahnstrasse mehr.
Ein abgelehntes Paar blieb bei jedem weiteren Suchlauf stumm, und nichts
verriet, dass es je einen Vorschlag gab. Wer einmal falsch klickte,
suchte danach einen Fehler in der Erkennung, den es nicht gab. Die
abgelehnten Paare lassen sich jetzt ansehen und zurueckholen.
Und wenn die Vorschlaege nicht geladen werden koennen, wird das gesagt
statt verschluckt – sonst sieht die Liste aus, als gaebe es nichts zu
verbinden.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eine Saeule 3a ist gesperrt – bis zur Rente, zur Selbstaendigkeit oder
zur Auswanderung. Bisher zaehlte ihr Stand in dieselbe Summe wie das
Girokonto, und die Antwort auf "wie viel habe ich" war um genau diesen
Betrag zu hoch. Das Geld steht da und ist doch nicht da.
Konten lassen sich jetzt als gebunden kennzeichnen. "Gesamt" zeigt dann
nur noch, was wirklich ausgegeben werden kann; darunter steht
"zusaetzlich CHF 2'004.61 gebunden" – aufklappbar, mit jedem Konto
einzeln. Eine Summe, die man nicht nachrechnen kann, muss man glauben.
Verschwiegen wird nichts: Der Saldo bleibt am Konto stehen, und
Vermoegensverlauf wie Nettovermoegen rechnen weiter damit. Es nicht
ausgeben zu koennen heisst nicht, es nicht zu besitzen – das ist der
ganze Unterschied, und ein Test haelt ihn fest.
Ein eigenes Kennzeichen, keine Ableitung aus dem Kontotyp: Ein frei
verkaeufliches Depot und eine gesperrte Saeule 3a kommen beide als
"depot" herein. Am Typ laesst sich das nicht ablesen – am
ausdruecklichen Eintrag schon.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Abgleich wird einmal serverseitig gerechnet, beim Erzeugen der
Vorschau. Wer danach eine Zeile von Hand umstellt, erreicht ihn nicht –
das gruene "stimmt" blieb stehen, obwohl es sich auf eine andere
Leserichtung bezog.
Mit der neuen Sammelaktion "Richtung umdrehen" waere das leicht
passiert: ein Klick, und die Bestaetigung darueber stimmt nicht mehr.
Nachgerechnet wird nicht – das waere eine zweite Wahrheit neben der
ersten. Stattdessen sagt der Hinweis, dass seine Aussage veraltet ist.
Beim Einlesen kommt die Art aus dem Vorzeichen; jede Abweichung ist
also nachweislich eine Handaenderung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisher standen alle Einstellungen offen nebeneinander – auch die, die
Certain laengst erkannt hatte. Rund zwanzig Bedienelemente fuer einen
Vorgang, bei dem im Normalfall keine einzige Entscheidung ansteht.
Jetzt: Datei waehlen, Pruefansicht. Dazwischen nichts.
- Format und Spaltenzuordnung klappen ein, solange die Erkennung sicher
ist. Ein Klick auf "Alle Einstellungen anzeigen" holt sie zurueck –
samt der Begruendung, WAS erkannt wurde und warum die Vorschau so
aussieht, wie sie aussieht. Wer nachsehen will, kann; muessen soll
niemand.
- Die Erkennungs-Meldung steht jetzt unter der Datei, auf die sie sich
bezieht, statt weit unten zwischen den Spaltenfeldern.
- "Vorschau erstellen" entfaellt. Der Knopf tat genau das, was ohnehin
als naechstes kommen musste; die Vorschau laeuft jetzt von selbst,
entprellt, sobald Datum und Betrag zugeordnet sind. Fehlt etwas, steht
da, was fehlt.
- "Als Vorlage speichern" ist keine primaere Aktion mehr. Das ist Pflege
und sah aus wie eine Entscheidung, die jetzt ansteht.
Der Zeilenfilter klappt ausdruecklich NICHT mit ein. Revolut legt alle
Pockets in eine Datei; ohne Filter zaehlen die Bewegungen doppelt. Das
ist kein Detail fuer Fortgeschrittene, sondern die Bedingung dafuer,
dass die Zahlen stimmen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eine Kreditkarte bekommt Geld nur aus einem Grund: weil ihre Rechnung
bezahlt wird. Das genuegt als Hinweis – und es wird gebraucht, denn im
Buchungstext steht bei so einer Zahlung oft weder ein Schlagwort noch
der Name des Gegenkontos. Die Erkennung fand das Paar deshalb, liess es
aber als Vorschlag liegen.
Entscheidend ist die RICHTUNG, nicht dass eine Karte beteiligt ist. Die
naheliegende Fassung "Kartenkonto auf einer Seite" haette einen
Karteneinkauf ueber 50,00 mit einer zufaelligen Gutschrift ueber 50,00
am selben Tag verknuepft – eine echte Ausgabe UND eine echte Einnahme
waeren still aus der Auswertung gefallen. Genau davor schuetzt die
Huerde, die hier gelockert wird. Ein Test haelt die Gegenprobe fest.
Die Schlagwortliste bleibt unberuehrt: "visa" und "mastercard" stehen in
gewoehnlichen Buchungstexten jedes Kontos.
Und der Import sagt jetzt, was er getan hat. Bisher lief die Suche nach
dem Buchen still; wer wissen wollte, ob die Kartenrechnung erkannt
wurde, musste auf "Zu erledigen" wechseln – und ahnte nicht, dass es da
etwas zu sehen gab. Was nicht automatisch geht, erscheint als Vorschlag
direkt auf der Import-Seite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Auf einem Kartenauszug ist +6,00 eine AUSGABE und -10,00 die Zahlung der
Rechnung. Certain kannte diese Leserichtung nicht. Der scheinbare Ausweg
"Ein Betrag (immer Ausgabe)" machte auch die Rechnungszahlung und jede
Gutschrift zur Ausgabe.
Neuer Betragsmodus "signed_inverted". Er dreht Betrag UND Saldospalte:
Ein Auszug, der einen Einkauf als +6 fuehrt, schreibt auch den Saldo so –
die Schuld steigt. Wer nur den Betrag dreht, bekommt bei jeder einzelnen
Zeile eine Abweichung gemeldet, und die einzige Kontrollinstanz des
Imports wird wertlos. Dazu gibt es jetzt einen Test.
Waehlen muss den Modus niemand: /imports/detect erkennt jetzt auch das
KONTO – erkenne_konto konnte das laengst, benutzt hat es nur der
Telefon-Kurzbefehl. Ist das erkannte Konto eine Kreditkarte, wird die
Leserichtung mitgesetzt. Eng gefasst: nur bei einer Betragsspalte, nur
aus "signed" heraus, nur bei sicher erkanntem Konto. Und nie still – in
der Pruefansicht steht, dass umgekehrt gelesen wird, und wo man es
aendert.
Drei Dinge, die dabei auffielen:
- Der Abgleich rechnete mit dem Vorzeichen, gebucht wird aber nach Art
(abs(Betrag) + Ausgabe/Einnahme). Wer eine Zeile umstellte, pruefte
eine Buchung, die so nie entsteht.
- Dieselbe Luecke in der Vorschau: Die Betragsspalte faerbte nach dem
Rohvorzeichen. Ein Umstellen aenderte sichtbar nichts.
- Auf Kartenauszuegen heisst die Empfaengerspalte "Haendler", und viele
fuehren die eigene Kontonummer in jeder Zeile mit. Die gewann die
Spaltenwahl ueber die Textlaenge – heraus kam eine Liste, in der bei
jeder Buchung dasselbe stand.
Neu ausserdem eine Summenzeile je Richtung und eine Sammelaktion: Ist
eine Datei durchgehend verkehrt herum gelesen, sind alle Zeilen
betroffen, und bei 150 Positionen ist Einzelkorrektur keine Moeglichkeit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wer seinen Kreditrahmen festhalten wollte, hatte dafuer nur den
Anfangssaldo. Also standen dort 2.500, und Certain meldete ein Vermoegen,
das es nicht gibt: Das Geld ist nicht da, es darf nur ausgegeben werden.
Jetzt hat der Rahmen ein eigenes Feld – sichtbar nur bei Kreditkarten,
denn an einem Girokonto gaebe es nichts zu begrenzen. Die Kontokarte
zeigt daneben, was noch geht ("noch 1.754,50 von 2.500,00 frei") und
warnt, wenn der Rahmen ueberzogen ist.
Der Saldo selbst bleibt, was er ist: abbezahlt null, jede Ausgabe ins
Minus, die Rechnung holt ihn zurueck. Gerechnet werden musste dafuer
nichts neu – die Saldenrechnung war schon vorzeichenoffen.
Zwei Dinge fielen dabei auf und sind miterledigt:
- Die Summe "Gesamt" auf der Kontenseite zaehlte archivierte Konten mit,
das Backend nicht. Zwei Zahlen fuer dieselbe Sache.
- "Gesamt" wurde ungefaerbt ausgegeben. Solange nur Guthaben
zusammenkamen, fiel das nicht auf; mit einer Kartenschuld stuende ein
negatives Gesamt still und unauffaellig da.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein Abo stand bisher unverbunden neben seinen Zahlungen. Wer wissen wollte,
ob Netflix diesen Monat abgebucht wurde, musste es in der Buchungsliste
suchen. Jetzt verknuepft Certain Buchung und Vertrag beim Import selbst.
Drei Stufen, bewusst unterschiedlich mutig:
- sicher (Betrag und Text passen, Datum im Fenster): wird zugeordnet.
- Vorschlag: Betrag passt, Text nicht – oder der Text passt und der Betrag
weicht ab. Der zweite Fall ist die Preiserhoehung.
- nichts: im Zweifel lieber schweigen.
Eine Preiserhoehung wird nie stillschweigend uebernommen. Sie ist eine
Nachricht an den Nutzer, kein Wartungsvorgang – er entscheidet, ob der
neue Preis der richtige ist.
Die Zuordnung ist ein Etikett, keine Umbuchung: die Buchung selbst bleibt
unangetastet, und ein geloeschter Vertrag nimmt sie nicht mit.
Beim Backup wandern Vertraege nun vor die Buchungen, sonst haenge eine
Rueckspielung Zahlungen an die falschen Vertraege.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aendert der Anmeldedienst den Namen eines Nutzers, muss Certain folgen
koennen. Bisher ging das gar nicht: Umbenennen war nirgends vorgesehen.
Die gute Nachricht vorweg – am Fundament war nichts zu richten. Buchungen,
Konten, Vertraege, Sitzungen und Geraeteschluessel haengen alle an der NUMMER
des Nutzers; der Name ist nirgends ein Fremdschluessel, und auch das
Wiedereinspielen einer Sicherung ordnet nicht ueber ihn zu. Umbenennen zieht
also nichts mit. Ein Test haelt das jetzt fest: Nach dem Umbenennen sind es
dieselbe Nummer, dasselbe Konto und derselbe Saldo.
Zwei Wege gibt es jetzt.
Der Admin kann umbenennen (PATCH /api/users/{id}). Eindeutig bleiben muss der
Name trotzdem – er ist das, was beim Passwort-Login eingetippt wird.
Und der Anmeldedienst zieht bei jeder Anmeldung nach. Dabei zaehlt, WESSEN
Name das jeweils ist: Der Anzeigename gehoert dem Anbieter – heiratet jemand,
soll der neue Name auch hier stehen. Der Benutzername gehoert bei einem Konto
MIT Passwort dem Nutzer; ihn hinter dessen Ruecken zu aendern hiesse, ihm
seine Anmeldung wegzunehmen. Nur bei einem reinen SSO-Konto gibt es nichts
einzutippen – dort darf der Anbieter bestimmen.
Ist der neue Name schon vergeben, bleibt der alte stehen. Eine Anmeldung
daran scheitern zu lassen waere die schlechtere Antwort.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Auf "Mit Authentik anmelden" folgt der BROWSER einem Link – da steht kein
fetch(), das eine Fehlerantwort auswerten koennte. Scheiterte der Aufruf des
Anmeldedienstes (Tippfehler in der Discovery-Adresse, Name nicht aufloesbar),
landete der Nutzer auf einer weissen Seite mit {"detail": ...} und musste
zurueckdruecken.
Jetzt geht es zurueck in die Oberflaeche, mit dem Grund in der Adresse – genau
wie bei der Rueckkehr vom Anbieter. Beim Anmelden auf die Login-Seite, beim
Verknuepfen auf "Mein Konto".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Adresse, an die der Anmeldedienst zurueckschickt, wurde aus der Anfrage
abgeleitet. Hinter einem Proxy ist das die INTERNE – http://10.0.0.5:8071/… –,
denn uvicorn lief ohne --proxy-headers. Der Anmeldedienst haette die
Rueckleitung abgewiesen, mit einer Meldung, die nicht sagt, woran es liegt.
APP_PUBLIC_BASE_URL sagt es ihm jetzt ausdruecklich. Leer bleiben darf sie
weiterhin: ohne Proxy und in der Entwicklung genuegt die Anfrage.
Die Referenz-Unit bekommt zusaetzlich --proxy-headers. Das hilft neuen
Installationen; bestehende ruehrt update.sh nicht an, deshalb ist die
Einstellung der verlaessliche Weg.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein zweiter Weg herein, kein Ersatz. Wer weiter Passwort und Zwei-Faktor
benutzen will, merkt nichts: Ohne eingerichteten Anbieter sieht die
Login-Seite aus wie bisher.
DER WEG FUER BESTEHENDE KONTEN ist das Verknuepfen, nicht das Anmelden: einmal
wie gewohnt hereinkommen, unter "Mein Konto" auf "Mit Authentik verknuepfen",
fertig. Danach geht beides.
Was es ausdruecklich NICHT gibt, ist das automatische Zusammenfuehren ueber
die E-Mail. Es waere der bequeme Weg und der gefaehrliche: Wer beim Anbieter
eine fremde Adresse eintraegt, uebernaehme damit ein fremdes Konto – und die
E-Mail ist dort die Angabe, die sich am leichtesten aendern laesst. Deshalb
sagt der Nutzer selbst, wer er beim Anbieter ist, im angemeldeten Zustand.
Aus demselben Grund legt eine unbekannte Anmeldung standardmaessig KEIN Konto
an, sondern wird mit dem Hinweis abgewiesen, sich anzumelden und zu
verknuepfen. Sonst bekaeme genau der Mensch, der schon ein Konto hat, bei
seiner ersten SSO-Anmeldung ein zweites, leeres – und seine Daten laegen
weiter unter dem alten. Wer will, schaltet AUTO_CREATE ein.
Geprueft wird mit Authlib, nicht von Hand: Signatur gegen JWKS, Aussteller,
Empfaenger, Laufzeit und nonce. Dazu state gegen untergeschobene Rueckkehr und
PKCE, obwohl der Client vertraulich ist – es kostet nichts und der Code taugt
damit auch fuer einen oeffentlichen.
Ein zweiter Anbieter ist reine Konfiguration:
APP_OIDC_PROVIDERS=authentik,keycloak
Nebenbei zwei Stellen abgesichert, die es vorher nicht geben konnte: Ein Konto
ohne Passwort faellt beim Passwort-Login nicht mehr in argon2 (das haette
verraten, dass es diesen Benutzernamen gibt), und die letzte Verknuepfung
laesst sich nicht loesen, solange es kein Passwort gibt – sonst schliesst sich
der Nutzer aus seinem eigenen Konto aus.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Hinweis stand unter der Zeile und begann mit "davon". Die naechste Zahl
darueber ist der Ist-Betrag, also las es sich als "von den 17.66 sind 113.72
gebunden" – und wo die Vertraege mehr kosten als geplant, ergab es vollends
keinen Sinn. Die Rechnung stimmte; der Satz war falsch.
Jetzt steht schlicht da, was es ist: "4 Vertraege kosten CHF 113.72 im Monat".
Kein "davon", kein Bezug auf eine Zahl, zu der es keinen gibt. Kostet mehr als
geplant, sagt die Zeile das ausdruecklich, statt es der Rechnung zu ueberlassen.
Vor allem aber laesst sich die Summe jetzt AUFKLAPPEN: darunter steht jeder
einzelne Vertrag mit seinem Monatswert, und bei laengerem Intervall auch der
urspruengliche Betrag ("CHF 190.00 jaehrlich · 16.84 EUR"). Eine Summe, die man
nicht nachrechnen kann, muss man glauben – und wenn sie einem krumm vorkommt,
hilft nur Nachsehen.
Die Liste kommt aus derselben Quelle wie die Summe (monatlich_base_cents vom
Server). Stimmten die beiden nicht ueberein, waere das ein echter Fehler – und
man saehe ihn sofort.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Vertraege standen bisher nur auf ihrer eigenen Seite. Im Budget sah man
nichts davon – man musste selbst nachrechnen, was fest gebunden ist, und beim
naechsten Preisaufschlag noch einmal.
Jetzt hat jede Kategorie zwei Betriebsarten, umschaltbar ueber ein Sigma neben
dem Eingabefeld:
fest eine Zahl, die man hinschreibt. Wie bisher.
berechnet Summe der Vertraege dieser Kategorie plus einem Zuschlag.
Der Zuschlag ist der Punkt. Wer fuer Streaming 62 plant, weil 42 gebunden sind
und 20 Luft sein sollen, muss beim naechsten Preisaufschlag nicht nachrechnen
– und verliert seine 20 dabei auch nicht. Unter der Zeile steht die Rechnung
offen, sonst stuende im Feld eine Zahl, die nicht der Soll-Betrag ist.
Das gilt fuer JEDE Kategorie, nicht nur fuer eine Abo-Kategorie: Ein
Handyvertrag liegt in "Telefon & Internet", und die rechnet sich genauso.
Auch auf "fest" steht der Hinweis da ("davon 16.84 fest gebunden") – sonst
sieht man nie, was einen schon bindet.
Zwei Dinge, die sonst still danebengingen, melden sich jetzt oben auf der
Seite: Vertraege OHNE Kategorie (ihr Geld fehlt im Budget, ohne dass irgendwo
steht, dass es fehlt) und Waehrungen ohne Kurs.
Gerechnet wird in effective_planned – der einen Stelle, aus der Budget-Seite
UND Dashboard ihr Soll beziehen. Damit koennen die beiden gar nicht erst
auseinanderlaufen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zoomen war das falsche Werkzeug. Man konnte nur naeher heran, aber nicht
sagen, WAS man behalten will – und das Ergebnis war immer quadratisch, auch
wenn das Logo breit ist.
Jetzt liegt ein Rahmen auf dem Bild: ziehen zum Verschieben, acht Griffe zum
Aufziehen. Aussen abgedunkelt, Drittel-Linien zum Ausrichten, und darunter
steht, wie gross der Ausschnitt gerade ist. "Ganzes Bild" setzt zurueck.
Das Seitenverhaeltnis des Ausschnitts bleibt. Ein breites Logo wird ein
breites Logo, kein quadratisches mit Luft an den Seiten.
Der Ausschnitt wird in BILDPUNKTEN gefuehrt, nicht in Bildschirmpunkten –
umgerechnet wird nur fuer die Anzeige. So trifft das Ergebnis genau den
Bereich im Rahmen, ohne dass sich ueber viele Zieh-Bewegungen Rundungsfehler
aufaddieren.
Am Bildrand haelt die gegenueberliegende Kante still, statt dass der Rahmen
beim Anschlag mitwandert; zieht man einen Griff ueber die eigene Kante
hinaus, bleibt sie stehen, statt sich umzustuelpen. Auf dem Telefon sind die
Griffe groesser – mit dem Finger waeren vierzehn Pixel nicht zu treffen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein Screenshot von einer Website ist selten schon das Logo – da ist Rand
drumherum oder drei Marken nebeneinander. Nach dem Waehlen kommt deshalb ein
Rahmen: ziehen zum Verschieben, Regler zum Zoomen.
Der Rahmen IST der Ausschnitt. Deshalb nichts abgedunkelt und keine
Hilfslinien darin – man soll das Ergebnis sehen und nicht eine Vorschau
davon, die man erst uebersetzen muss.
Zu Beginn steht das GANZE Bild im Rahmen. Wer nicht zuschneiden will, drueckt
Uebernehmen und bekommt, was er hochgeladen hat; Zuschneiden ist ein Zoom,
kein Zwang. Verschieben geht nur, solange das Bild den Rahmen ueberragt –
sonst schoebe man ein kleines Bild aus seinem eigenen Ausschnitt heraus.
Geschnitten wird im Browser, hochgeladen nur noch der Ausschnitt: Der Server
bekommt kein Bild, das er gar nicht behalten soll.
Der durchsichtige Grund bleibt durchsichtig – ein weisser Kasten fiele auf
dunklem Hintergrund sofort auf.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Am Logo findet man den Vertrag schneller als am Namen: Netflix erkennt man am
roten N, nicht am Wort – und erst recht nicht an einem Zahnrad aus der
Symbolauswahl. Die bleibt als Rueckfall fuer alles, wozu es kein Logo gibt.
Hochgeladen wird, was das Telefon hergibt; gespeichert ein PNG von hoechstens
128 Pixeln Kantenlaenge. Aus drei Megabyte werden ein paar Kilobyte, und das
Seitenverhaeltnis bleibt – ein verzerrtes Logo sieht falscher aus als ein
kleines.
Immer PNG, egal was hereinkam. Das erspart der Anzeige jede
Fallunterscheidung und schliesst SVG aus: Eine SVG-Datei kann Skript
enthalten, und ein Bild, das der Nutzer hochlaedt und der Browser ausfuehrt,
ist genau die Art Falle, die man sich nicht in ein Finanzprogramm legt.
Eigene Tabelle, nicht eine Spalte an den Vertraegen: Sonst zoege jede
Listenabfrage saemtliche Bilder mit heraus, obwohl die Liste sie einzeln
nachlaedt.
ZWEI FEHLER IM SICHERN, die dabei aufgefallen sind:
Binaeres liess sich gar nicht sichern – die Ausgabe scheiterte an den Bytes,
und zwar erst beim Schreiben, also nachdem der Nutzer glaubt, ein Backup zu
haben. Jetzt Base64.
Und "Ergaenzen" legte Zeilen von Tabellen mit natuerlichem Schluessel ein
zweites Mal an, statt sie wiederzuerkennen. Beim Logo kippte damit die ganze
Wiederherstellung. Betraf bisher keine Tabelle, weil noch keine solche einen
Vergleichsschluessel hatte.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Sachen, die beim Benutzen aufgefallen sind.
ANGEBOTENE WAEHRUNGEN. In den Einstellungen steht jetzt, welche Waehrungen in
den Auswahlfeldern erscheinen (Vorgabe EUR, CHF, USD). Dass dreissig
unterstuetzt werden, ist gut; dreissig Zeilen in einem Feld zu sehen, wenn man
drei davon braucht, ist es nicht.
Die Einstellung beschraenkt nur die ANZEIGE. Gueltig bleibt alles mit
EZB-Kurs, und die gerade gesetzte Waehrung steht IMMER im Feld – sonst stuende
ein Konto in Dollar vor einem Feld ohne Dollar und wechselte beim naechsten
Speichern stillschweigend die Waehrung. Der Saldo waere danach still falsch.
Die Hauptwaehrung laesst sich nicht abwaehlen.
ZEICHEN STATT KUERZEL, wo es hilft: "€ EUR", "$ USD", "£ GBP". CHF steht
bewusst ohne: "Fr." ist weniger gelaeufig als CHF selbst. Mehrfach belegte
Zeichen bleiben ebenfalls draussen – "US$" von "CA$" zu unterscheiden ist
Raetselraten.
AUF DEM TELEFON. Die Vertragszeile quetschte vier Spalten in 375 Pixel; vom
Namen blieb "Sal..." uebrig und von der Kategorie ein Punkt. Sie ist jetzt ein
Gitter: Unter 620 Pixel rutscht der Betrag unter den Namen, die Knoepfe
bleiben rechts. Der Name bekommt die volle Breite – auf dem Telefon ist er das
Einzige, woran man die Zeile erkennt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Netflix, Handyvertrag, Krankenkasse, Halbtax. Bisher sah man sie nur als
einzelne Buchungen im Auszug; die Frage "welche Abos habe ich eigentlich, und
was kosten die zusammen im Monat" liess sich nicht beantworten. Genau die
steht jetzt oben auf der Seite.
Der Bereich heisst "Abos & Vertraege", nicht "Abos": Ein Handyvertrag ist kein
Abo und die Versicherung erst recht nicht – niemand soll ueberlegen muessen,
wo sie hingehoert.
Der Monatswert wird gerechnet, nicht gespeichert (Betrag durch Intervall).
Gespeichert veraltete er beim naechsten Preisaufschlag stillschweigend.
Bei laengerem Intervall klappt "Ich lege monatlich zurueck auf" auf, mit dem
gerechneten Monatsbetrag daneben. Bei monatlicher Abrechnung gibt es nichts
anzusparen, also steht es da auch nicht.
DAZU: Waehrungen sind offen.
Die Rechen-Maschinerie konnte immer schon beliebige Waehrungen – sie rechnet
ueber den Euro als Drehpunkt. Beschraenkt haben es zwei Konstanten: eine
Erlaubnisliste mit EUR und CHF, und ein Filter, der aus dem EZB-Feed alles
ausser CHF wegwarf, obwohl er dreissig Waehrungen liefert.
Massgeblich ist jetzt die Liste der EZB. Frei erfundene Waehrungen bleiben
draussen, und das ist kein Geiz: Ohne Kurs liesse sich der Betrag nie
umrechnen – er stuende in keiner Summe und in keinem Budget, und das faellt
erst spaet auf. Ein Vertrag in einer Waehrung ohne Kurs wird deshalb genannt,
statt die Summe stillschweigend zu klein anzuzeigen.
Erkannt und gebucht wird noch nichts; der Erkennungstext wird nur erfasst.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sparen zaehlt im Budget wie eine Ausgabe – zu Recht, das Geld ist diesen Monat
gebunden. Es stand danach aber in Rot da, und wer tausend zuruecklegte statt
achthundert, bekam eine Warnung fuer etwas Gutes.
Ein Haken an der Kategorie stellt das ab. Zwei Folgen:
Der Betrag steht neutral. NICHT gruen: Gruen heisst in Certain ueberall "Geld
kommt rein", und CHF 1'010 in Gruen liest sich als Einnahme. Grau sagt genau
das Richtige – kein Verlust, kein Gewinn.
Und mehr als geplant ist keine Ueberschreitung mehr. Das gilt auch eine Ebene
hoeher: Eine Gruppe misst sich an dem, was WIRKLICH zu viel war. Sonst faerbt
sich "Finanzen" rot, weil jemand gespart hat, und die echte Ueberschreitung
daneben faellt nicht mehr auf. Angezeigt werden weiter die vollen Summen; nur
das Urteil laesst das Sparen aus.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bei mehr als ein paar Konten rollte "Gesamt" mit aus der Karte heraus – die
eine Zahl, wegen der man auf die Karte schaut, war damit die einzige, die man
suchen musste.
Sie klebt jetzt am unteren Rand. Gilt auch fuer die Ausgaben-Anteile: dieselbe
Zeile, dasselbe Problem.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Dialog wuchs mit seinem Inhalt und lief unten aus dem Bild. Scrollen ging
nicht – damit war der Knopf schlicht weg, und das Konto liess sich nicht mehr
speichern.
Jetzt bleibt der Dialog im Fenster: Kopf und Knopfleiste stehen fest, nur der
Inhalt dazwischen rollt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wer den Haken an einer Kategorie setzt, sagt damit ausdruecklich: nicht ins
Budget, nicht ins Dashboard, nicht in die Statistik. Die Sparen-Auswertung
las ihn nicht – das Geld stand danach im Ringdiagramm, obwohl der Nutzer
genau das abbestellt hatte.
Beide Uebertrags-Wege halten sich jetzt daran, der am Stueck wie der aus zwei
verknuepften Buchungen. Der Haken an der Kategorie gewinnt; ein Sparziel am
Konto darf ihn nicht stillschweigend aushebeln.
Und weil beides zusammen keinen Sinn ergibt, sagt es das Konto-Formular jetzt
auch: Waehlt man dort eine Kategorie mit Haken, steht darunter, dass so nichts
im Budget erscheinen wird.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Laden der Kategorien landete in der falschen Komponente – in
TransferKeywords statt in Accounts. Dort gibt es kein `groups`, also warf die
Seite beim Aufbau "groups is not defined" und blieb leer.
Der Bau lief trotzdem durch: Es ist gueltiges JavaScript, nur eben mit einem
Namen, den es an dieser Stelle nicht gibt. Auffallen konnte das erst beim
Oeffnen der Seite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wer auf ein Pocket spart, budgetiert dafuer eine Ausgabe – im Budget stand
davon aber nichts. Zwei Gruende:
Erstens gibt es zwei Formen von Uebertrag. Die eine ist EINE Buchung mit
Zielkonto, wie sie das Formular anlegt; nur fuer die gab es die Sparen-Logik.
Die andere sind ZWEI verknuepfte Buchungen, und genau so kommt ein Uebertrag
ueber die Bankanbindung herein. Beide Seiten uebersprang die Auswertung
ausnahmslos.
Zweitens hing das Sparen am Kontotyp. Ein Revolut-Pocket ist bei der Bank ein
ganz normales Konto und kommt als "girokonto" herein – am Typ ist nicht
abzulesen, dass man darauf spart.
Jetzt sagt man es dem Konto einmal ("Sparen gegen Kategorie X"), und es gilt
fuer beide Formen. Wer eine einzelne Bewegung anders sieht, nimmt sie an der
Buchung heraus oder gibt ihr eine eigene Kategorie – dieselben zwei Felder wie
bisher.
Der Normalfall aendert sich nicht: Ein Uebertrag auf ein Konto ohne Sparziel
ist weiterhin bloss verschobenes Geld.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisher lag sie nur am Eingangs-Eintrag. Das reicht nicht: Der Eintrag ist eine
Zwischenstation und faellt weg, wenn ein Konto geloescht wird oder ein aelteres
Backup ihn nicht kannte. Dann war die Zahlung fuer den naechsten Abruf neu,
obwohl sie laengst gebucht war.
Am schlimmsten bei einem als Uebertrag gebuchten Umsatz: Der ist von der
Aehnlichkeitspruefung ausgenommen, dort griff also gar nichts mehr.
Die Kennung gehoert deshalb an die Buchung (transactions.source_uid, mit der
Quelle davor). Dort ueberlebt sie alles, was mit dem Eingang passiert, und die
Eindeutigkeit haelt die Datenbank selbst durch – nicht erst der pruefende Code
davor. Gestempelt wird an allen drei Buchungswegen: Eingang, Kurzbefehl und
Uebertrag.
Vorhandene Buchungen bekommen die Kennung nachgetragen, solange ihr
Eingangs-Eintrag noch da ist.
Ohne Kennung – von Hand erfasst, aus einer CSV-Datei – bleibt es beim
bisherigen Schutz ueber die Aehnlichkeit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Schlagwort-Knopf sah bei jeder Kategorie gleich aus - man musste jede
einzeln oeffnen, um zu sehen, wo ueberhaupt etwas hinterlegt ist. Belegte
Kategorien tragen den Knopf jetzt in Akzentfarbe und mit der Anzahl daneben,
der Tooltip nennt sie ausgeschrieben.
Die Zahl kommt als keyword_count an der Kategorie mit, ermittelt in einer
Aggregat-Abfrage fuer die ganze Liste. Ein Abruf je Kategorie waere dafuer
unverhaeltnismaessig gewesen. Gefuellt wird das Feld nur beim Auflisten;
beim Anlegen und Aendern einer Gruppe bleibt es bei 0, wo es ohnehin nichts
aussagt.
Das Popup laedt beim Schliessen die Liste neu, damit die Zahl nach dem
Bearbeiten stimmt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
In der Passwortrichtlinie stand das Haekchen mittig und der Text ganz rechts.
Ursache war die globale Regel "input, select { width: 100% }": sie erwischt
auch Ankreuzfelder und zieht sie ueber die volle Breite. Vier Stellen im
Code umgingen das bereits einzeln (.checklist, .dt-pop-opt, .checkline,
.datatable td) - eine fuenfte Umgehung waere die falsche Antwort gewesen.
Ankreuz- und Auswahlfelder sind jetzt zentral ausgenommen, die vier
Einzelfaelle entfallen. Nebeneffekt: sie tragen ueberall dieselbe
Akzentfarbe (bisher nur an zwei Stellen) und sitzen in Tabellenzellen
mittig, statt durch das geerbte margin-top um 5px nach unten gedrueckt zu
werden.
Die Karte selbst nutzt jetzt .checkline, das vorhandene Muster fuer
"Ankreuzfeld plus Text"; .field-inline richtet an der Grundlinie aus und
war hier von vornherein das falsche.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisher oeffnete der Doppelklick ein Eingabefeld im Chip selbst. Dort war nur
Platz fuer das Wort - der Betrag liess sich gar nicht aendern, weil die
Inline-Bearbeitung bewusst nur { word } geschickt hat.
Jetzt laedt der Doppelklick Wort und Betrag in dieselbe Zeile, in der auch
neue Schlagwoerter entstehen. Der Knopf wechselt auf ein Haekchen, daneben
steht Abbrechen (auch per Escape), und der bearbeitete Chip ist markiert.
Gespeichert wird per PATCH mit ausdruecklichem amount_cents, damit ein
geleertes Betragsfeld den Betrag auch wirklich loescht; ein geparstes 0 wird
zu null, weil das Schema gt=0 verlangt. Ueber POST ginge es nicht: das
antwortet bei vorhandenem Wort mit geaendertem Betrag mit 409.
Tests decken jetzt ab, dass Betrag mitgeaendert wird, beim Weglassen stehen
bleibt und bei ausdruecklichem null verschwindet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Im Downloads-Ordner lagen "certain-finance-export_....json" und
"certain-finance-backup_....json" nebeneinander, ohne dass man sah, welche
Datei sich zurueckladen laesst. Alle Downloads heissen jetzt
certain-finance_<art>_<datum>_<uhrzeit>.<endung>, wobei die Art es sagt:
vollbackup, vollbackup-system, datenuebersicht, buchungen, datenbank. Das
persoenliche Vollbackup traegt zusaetzlich den Benutzernamen.
Den Namen vergibt jetzt allein der Server im Content-Disposition-Kopf; das
Frontend liest ihn von dort. Vorher erfanden beide Seiten ihn getrennt und
kamen auf unterschiedliche Ergebnisse (Bindestrich gegen Unterstrich, UTC
gegen Ortszeit). Zeitstempel ist Ortszeit: wer abends um 20 Uhr exportiert,
soll keine Datei von 18 Uhr finden.
Passend dazu heisst der Knopf "Alle Daten (JSON)" nun "Datenuebersicht
(JSON)", und der Hinweis darueber sagt ausdruecklich, dass sich diese
Dateien nicht zurueckladen lassen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei-Faktor per TOTP mit zehn Einmal-Wiederherstellungscodes. Der
Zwischenzustand "Passwort stimmt, Code fehlt" haengt an der vorhandenen
Sitzungstabelle (pending_2fa, zehn Minuten Laufzeit) und gilt nirgends als
angemeldet; nach dem Code wird das Cookie getauscht statt weiterverwendet.
Der Wiederverwendungsschutz merkt sich das tatsaechlich getroffene
Zeitfenster, nicht die aktuelle Uhrzeit - sonst liesse sich ein Code aus dem
naechsten Fenster gleich darauf nochmal einloesen.
Passwortrichtlinie (Laenge, Zeichenklassen, MFA-Pflicht) in der neuen
globalen Tabelle app_settings, durchgesetzt beim Anlegen, Zuruecksetzen und
Aendern von Passwoertern. Bestehende Passwoerter lassen sich nicht gegen eine
neue Richtlinie pruefen - der Hash gibt nichts her. Beim Anmelden liegt das
Passwort aber kurz im Klartext vor: dort wird geprueft und bei Verstoss ein
Wechsel verlangt, mit passendem Grund fuer den Hinweistext.
Nebenbei geschlossen: must_change_password war bisher nur im Frontend
erzwungen, wer die API direkt ansprach kam vorbei. enforce_account_state
haengt jetzt am /api-Sammelrouter und laesst nur noch durch, was man zum
Erfuellen der Pflicht braucht.
Das JSON-Backup liess sich bisher grundsaetzlich nicht zurueckladen: keine
Version, Kategorien nur ueber mehrdeutige Namen, sieben Tabellen fehlten.
Daneben steht nun ein Format v2, das Beziehungen ueber lokale IDs abbildet,
je Benutzer oder systemweit. Zurueckgeladen wird nach einer Pruefansicht,
wahlweise ersetzend oder ergaenzend; vor dem Ersetzen legt der Server einen
Schnappschuss der Datenbank an. Der alte Ansichts-Export bleibt fuer
Tabellenkalkulationen bestehen und wird beim Hochladen ausdruecklich
abgewiesen, statt halb zu raten.
Frontend: wiederverwendbares Passwortfeld mit Augensymbol, animierte
Anforderungsliste, 2FA-Seite, Admin-Karte fuer die Richtlinie und Karte zum
Zurueckladen. Neue Bewegung ist in prefers-reduced-motion hinterlegt; Secret
und Codes sind markierbar, sonst verhinderte das globale user-select das
Kopieren.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>