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>