Modul-Review 2: Onboarding-Umbau, Tour, Assistenten-Redesign
Deploy App / deploy (push) Successful in 1m10s

Ergebnis der zweiten Test- und Review-Runde (Modul "Onboarding & Plan-Erstellung"):

Layout:
- Szenario-Ansicht neu geordnet: schlanke Funktions-Leiste oben (Historie,
  Tour, Neues Szenario, Rechenwege, CSV-Export, Loeschen, Diff-Badge), dann
  Naechste Schritte -> Grundprofil -> Zeitachse -> Anzeige -> Matrix
- Grafiken/Effektive Werte/Live-Sim/MC/Einflussfaktoren aus der Leiste
  entfernt (laufen ueber die eigenen Menuepunkte)
- Tour nach AppShell gehoben; startet neu bei JEDER Plan-Erstellung (F14)

Assistent:
- durchgehend Du-Form im Einzelmodus
- Schritt "Vorsorge & Vermoegen" nur noch Bestandswerte
- neuer Schritt "Sparen & Verteilen": Sparquote (Netto - Ausgaben) auf
  3a/Wertschriften/Amortisation/Tilgung verteilen, Rest bleibt Cash; PK
  separat mit Hinweis "vor Netto, reduziert Sparquote nicht"
- Immobilie: Wertsteigerung + Zins-in-Ausgaben-Schalter; Lohn 1% Default

Grosse Saeule 3a (Selbststaendige ohne PK):
- Schalter am 3a-Element + im Assistenten hebt Obergrenze 7258 -> 36288
- neue Konstante PILLAR_3A_MAX_SELF_EMPLOYED (2026-Wert zu verifizieren)
- neues Feld selfEmployed3a

Nebenbei: POST /plans liefert lesbare Fehlermeldung statt zod-Objekt.

Kein Eingriff in den Rechenkern; 267 Tests unveraendert. SPEZIFIKATION 0.28.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 21:57:56 +02:00
parent 2bb243b896
commit 52c48a9956
8 changed files with 448 additions and 192 deletions
+48 -10
View File
@@ -4,10 +4,10 @@
| | |
|---|---|
| **Dokument** | Funktionale und Technische Spezifikation FPT |
| **Version** | 0.27 |
| **Version** | 0.28 |
| **Datum** | 2026-07-24 |
| **Status** | Lebendes Dokument |
| **Codestand** | Arbeitsstand nach `d6855ef` inkl. Sicherheits-Nachbesserungen Auth (Branch `main`) |
| **Codestand** | Arbeitsstand nach `2bb243b` inkl. Modul-Review 2 (Onboarding) (Branch `main`) |
| **Ersetzt** | `FDD_TDD_FPT.docx` (v1v5) im Ordner `Info Dateien` diese sind ab Version 0.1 dieses Dokuments obsolet |
| **Geltungsbereich** | Gesamter Code im Verzeichnis `FPT` |
@@ -17,6 +17,7 @@
| Version | Datum | Autor | Änderung |
|---|---|---|---|
| 0.28 | 2026-07-24 | Claude (Opus 4.8) | **Modul-Review 2 (Onboarding & Ansicht): Layout-Umbau, Tour und Assistenten-Redesign.** (1) **Szenario-Ansicht neu geordnet** (Kap. 3.7.7): oben eine schlanke Funktions-Leiste (Änderungshistorie · Tour · Neues Szenario · Rechenwege · CSV-Export · Löschen · Abweichungs-Badge), darunter Nächste Schritte → Grundprofil → Zeitachse → Anzeige-Umschalter → Matrix. Grafiken, Effektive Werte, Live-Simulation, Monte-Carlo und Einflussfaktoren sind aus der Leiste **entfernt** sie laufen über die eigenen Menüpunkte (Analysen / Effektive Werte). (2) **Tour** liegt neu in `AppShell` (statt `PlanView`) und startet nach **jeder** Plan-Erstellung Assistent, Beispielplan **und** leerer Plan unabhängig davon, ob sie schon einmal beendet wurde (Kap. 3.7.8); die Erfolgsmeldung hält damit ihr Versprechen. (3) **Assistent durchgehend in Du-Form** im Einzelmodus (Paarmodus weiter «ihr» / je Person). (4) **Schritt-Redesign** (Kap. 3.2.8): Schritt «Vorsorge & Vermögen» erfasst nur noch **Bestandswerte**; ein **neuer Schritt «Sparen & Verteilen»** zeigt die Sparquote (Nettoeinkommen Ausgaben) und lässt sie auf 3a, Wertschriften, Amortisation und Schuldtilgung verteilen der Rest bleibt sichtbar auf dem Cash-Konto. Die **PK-Einzahlung** steht dort bewusst separat, mit dem Hinweis, dass sie vom Bruttolohn kommt und die Sparquote **nicht** schmälert (deckt sich mit dem Rechenkern). Immobilien fragen im Assistenten neu **Wertsteigerung** und den **Zins-in-Ausgaben-Schalter** ab; der Lohn bekommt 1 % Default-Erhöhung. (5) **«Grosse Säule 3a» für Selbstständige** (Roadmap-Feedback): ein Schalter am 3a-Element (und im Assistenten) hebt die Beitrags-Obergrenze von 7258 auf ca. 36288 CHF an (neue Konstante `PILLAR_3A_MAX_SELF_EMPLOYED`, **2026-Wert zu verifizieren**; neues Feld `selfEmployed3a`). (6) Nebenbei: `POST /plans` liefert bei Validierungsfehlern eine lesbare Meldung statt eines rohen zod-Objekts. Keine Änderung am Rechenkern; 267 Tests unverändert grün. |
| 0.27 | 2026-07-24 | Claude (Opus 4.8) | **Modul-Review 1 (Zugang & App-Rahmen): fünf Nachbesserungen an der Authentifizierung.** Ergebnis der ersten gemeinsamen Test- und Review-Runde. (1) **Timing-Ausgleich beim Login:** Ein unbekannter Benutzer wird neu gegen einen Dummy-bcrypt-Hash geprüft, damit die Antwortzeit dieselbe ist wie bei einem bekannten -- vorher liess sich aus der Dauer ablesen, ob ein Benutzername existiert (Kap. 3.1.2). (2) **Zurück-Knopf nach Logout:** Die aus dem Browser-Cache (bfcache) zurückgeholte, eingefrorene Ansicht prüft neu beim `pageshow` die Session und leitet ohne Anmeldung sofort auf `/login` -- vorher blieb die alte Ansicht sichtbar (Kap. 3.1.3). (3) **Rate-Limiting:** Login (10/15 min je IP), Registrierung (5/h je IP) und Passwortänderung (10/15 min je Benutzer) sind gegen Durchprobieren gebremst; neues In-Memory-Modul `rate-limit.ts`, 429 mit `Retry-After` (Kap. 3.1.6). Der Login-Zähler läuft je IP **und Benutzername** -- ein Konto sperrt nicht die anderen Konten derselben IP. (4) **Passwort-Dialog** läuft neu über die zentrale `Modal`-Komponente und schliesst damit auf Esc (Fokus-Falle, aria-modal inklusive) -- war zuvor von Hand gebaut (Kap. 3.1.4). (5) **Registrierungs-Fehler** werden sauber getrennt: nur der belegte Benutzername ist ein 409 mit freundlicher Meldung, jeder andere Fehler ein 500 statt einer rohen Prisma-Meldung als Konflikt. 6 Tests ergänzt (261 → 267). Offen für Roadmap #34: kein Passwort-Längen-Maximum (bcrypt-72-Byte-Grenze), keine ARIA-Labels auf Login/Palette. |
| 0.26 | 2026-07-21 | Claude (Opus 4.8) | **Pensionsalter anpassen** (Roadmap Nr. 44, neue Kapitel 3.12, 4.4.7 und 4.16). Bisher war das Pensionsalter faktisch unantastbar: Es bestimmt, wo eine Lebensphase endet ein frei geändertes Alter hätte die Phasengrenze zerrissen. Neu wird nicht das Alter geändert, sondern **die Grenze verschoben**: Die Phase davor wird länger, die danach kürzer, die Gesamtdauer bleibt gleich. Der Spielraum endet dort, wo eine angrenzende Phase unter ein Jahr fiele; ein Schritt weiter **entfällt sie ganz**, was vorher bestätigt wird, weil dabei zwei Übergänge **zusammengelegt** werden (bereits getroffene Entscheide bleiben, leere Felder werden aus dem entfallenden Übergang ergänzt, einmalige Cash-Beträge werden addiert der Steuersatz betragsgewichtet). Das Pensionsalter ist damit auch **Treiber im Tornado** und **Regler in der Live-Simulation**. **AHV-Referenzalter (4.4.7):** Die Rente beginnt neu **immer mit 65**, unabhängig vom Pensionsalter wer länger arbeitet, erhält sie zusätzlich zum Lohn; wer früher aufhört, zahlt bis 65 einen **Beitrag als Nichterwerbstätige(r)**, der als laufende Ausgabe auf die Verzehrquote schlägt und mit 65 wegfällt (neues Feld `ahvContribution`, ohne Default, Hilfetext nennt die reale Bandbreite von rund 530 bis 26'500 CHF pro Jahr). Beide Wechsel können **innerhalb** einer Phase liegen, die AHV wird deshalb **jahresweise** statt phasenweise gerechnet. **Punkt B:** Ein Einkommen, das einer **Person** zugeordnet ist, fällt bei deren Pensionierung auf 0 bisher lief der Lohn stillschweigend in die Pension weiter. Gemeinsame Einkommen (Mieterträge o. Ä.) bleiben; ein ausdrücklich erfasster Betrag gewinnt, damit ein Teilzeitpensum modellierbar bleibt. **Punkt A:** Wiederkehr-Parameter (Raten, Beiträge, Amortisation, Wertsteigerung, Zinssatz) werden neu **live aus der Vorphase geerbt** statt beim Anlegen der Phase kopiert sichtbar als angehaktes **«Aus Vorphase übernehmen»** je Feld. Vorher blieb die Kopie stehen, wenn man die Vorphase später änderte. **Punkt C:** Der Kapitalzufluss am Pensions-Übergang (PK, 3a, Verkaufserlös) lässt sich in **Prozent** auf Amortisation, Anlage und Cash aufteilen bewusst nicht in Franken, weil sich der Betrag mit dem Pensionsalter ändert und eine Quote mitskaliert. **Nebenbei ein echter Fehler behoben:** Der fortgeschriebene Basiswert für Einkommen und Ausgaben wurde **vor** der Jahresschleife berechnet effektive Werte kamen dadurch nie in der Folgephase an. Neues Modul `retirement.ts`, neuer Endpunkt `POST /api/scenarios/<id>/retirement`; 40 Tests ergänzt (221 → 261). |
| 0.25 | 2026-07-21 | Claude (Opus 4.8) | **PDF-Berichte** (Roadmap Nr. 11, neues Kapitel 3.11). Neuer Unterpunkt **«Berichte»** auf Plan-Ebene: Liste der erzeugten Berichte plus Assistent zum Anlegen (Titel, Notiz, nominal **oder** real, Plan- oder effektive Daten, bis zu **drei** Szenarien, beliebige gespeicherte Analysen). Das **Layout ist immer gleich**; die Auswahl bestimmt nur, welche Bausteine erscheinen: Deckblatt mit Zusammenfassung und drei Kernaussagen, dann je Szenario Kennzahlen, Vermögensverlauf, Lebensphasen und Annahmen, danach Vergleich, Plan/Ist, Analysen und die Hinweise. **Die PDF-Datei wird als Datei abgelegt** (BYTEA in Postgres, nicht im Container-Dateisystem, das jeder Deploy neu baut): Ein Bericht muss in drei Jahren byte-identisch wieder herunterladbar sein eine Neuerzeugung könnte das nach Änderungen an Plan, Rechenkern oder Layout nicht garantieren. **Kennzahlen je Szenario:** Endvermögen, Kapitalreichweite, Vermögen und Vorsorgekapital bei Pensionierung, AHV- und PK-Rente sowie die **offenen Entscheide** die einzige unmittelbar handlungsleitende Zahl. Damit Bericht und Matrix nie verschiedene Zahlen nennen, liegt deren Zählung neu als reine Funktion in `decisions.ts`, die beide benutzen. **Zu jeder Kennzahl steht ihre Grundlage** als kurzer Verweis; die vollständigen Annahmen (Startwerte, Renditen, Raten je Element) stehen **einmal** je Szenario, statt bei jeder Kennzahl wiederholt zu werden. Ein **Haftungsausschluss** ist verpflichtend und durch einen Test gesichert ein formal gesetztes PDF wird sonst als Beratung gelesen. **Technik:** `pdfkit` in der Node-Runtime statt Headless-Browser (kein Chromium im Image); `@react-pdf/renderer` schied aus, weil es mit React 19 / Next 16 bricht. Diagramme entstehen als **echte Vektoren** aus den gespeicherten Zahlen genau dafür wurden die Analysen in 0.24 als Zahlen und nicht als Bilder abgelegt. `pdfkit` ist als externes Paket deklariert, weil es Font-Metriken über Dateipfade lädt und gebündelt erst in der Produktion bräche. Neue Tabelle `Report`, Endpunkte unter `/api/plans/<id>/reports`, neue Module `report.ts`, `report-pdf.ts`, `decisions.ts`; 9 Tests ergänzt (212 → 221). |
@@ -411,8 +412,10 @@ gesetzt. Kalenderjahr eines Planjahrs: `startYear + (Jahr 1)`.
### 3.2.8 Geführter Assistent und Beispielplan
**Der Plan-Assistent** (Roadmap Nr. 10: «Schritt für Schritt statt leerer Matrix») fragt in
fünf Schritten in Alltagssprache: (1) Grundprofil, (2) Lebensphasen, (3) Einkommen und Ausgaben
plus Kontostand, (4) Vorsorge und Vermögen, (5) Zusammenfassung.
**sechs** Schritten in Alltagssprache: (1) Grundprofil, (2) Lebensphasen, (3) Einkommen und
Ausgaben plus Kontostand, (4) Vorsorge und Vermögen (**nur Bestandswerte**), (5) Sparen und
Verteilen, (6) Zusammenfassung. Im **Einzelmodus** durchgehend in **Du-Form** («Was verdienst
du?»); im Paarmodus je Person bzw. «ihr».
**Schritt 2 ist an den fixen Pensionierungszeitpunkten ausgerichtet.** Das Pensionsalter jeder
Person ist ein Fixpunkt auf der Lebenslinie; dazwischen entstehen Abschnitte mit konstantem
@@ -444,7 +447,22 @@ Phasen lesbar bleiben.
**Schritt 4 (Vorsorge & Vermögen)** ist bei Paaren in **Gemeinsam / Person A / Person B**
aufgeteilt. Pensionskasse und Säule 3a sind immer persönlich (personengebundene Kategorien);
Wertschriften, Wohneigentum und Schulden lassen sich gemeinsam **oder** je Person erfassen.
Wertschriften, Wohneigentum und Schulden lassen sich gemeinsam **oder** je Person erfassen. Hier
werden nur die **heutigen Bestandswerte** erfasst (Guthaben, Kaufpreis, Hypothek, Restschuld)
die laufenden Jahresbeträge folgen in Schritt 5. Zusätzlich fragt das Wohneigentum die
**Wertsteigerung** und den **Zins-in-Ausgaben-Schalter** ab (damit Hypothekarzinsen nicht
doppelt zählen), und die Säule 3a einen Schalter **«Selbstständig ohne PK (grosse Säule 3a)»**,
der die Beitrags-Obergrenze anhebt (siehe 4.11 / Feld `selfEmployed3a`).
**Schritt 5 (Sparen & Verteilen)** bringt das Kernmodell des Tools zum Anfassen: Aus
`Nettoeinkommen Ausgaben` entsteht die **Sparquote**; der Nutzer verteilt sie auf Säule 3a,
Wertschriften, Amortisation und Schuldtilgung, und der **Rest bleibt sichtbar auf dem
Cash-Konto** (live gerechnet, negativer Rest wird gewarnt). Die **PK-Einzahlung** steht bewusst
in einem **eigenen** Block mit dem Hinweis, dass sie vom **Bruttolohn** bezahlt wird also
**vor** dem Nettoeinkommen und die Sparquote deshalb **nicht** schmälert. Das deckt sich exakt
mit dem Rechenkern, wo der PK-Beitrag nicht zur Quote zählt ([4.6.3](#463-pension_fund)). Der
Schritt entfällt fachlich nie, aber wenn keine Spar-/Vorsorgeposten angehakt sind, weist er nur
darauf hin, dass der ganze Betrag auf dem Cash-Konto wächst.
Zwei bewusste Entscheide bleiben:
- **Einkommen wird pro Person erfasst**, nie als «Gemeinsam» in Paar-Plänen zählt
@@ -1273,13 +1291,31 @@ Neuaufbau des Formulars wie zuvor bei den Dialogen.
Der frühere Dialog «Plan-Einstellungen» heisst im Panel korrekt **«Szenario-Profil»** er
bearbeitet seit V6 das Szenario, nicht den Plan.
**Aufbau der Szenario-Ansicht (seit 0.28):** von oben nach unten eine schlanke
**Funktions-Leiste** (Änderungshistorie · Tour · Neues Szenario aus diesem · Rechenwege ·
CSV-Export · Szenario löschen · Abweichungs-Badge), dann **Nächste Schritte**, das
**Grundprofil**, die **Zeitachse**, der **Anzeige-Umschalter** (Nominal/Beide/Real, Plan/Ist)
und zuunterst die **Matrix** mit ihren «Element»-/«Lebensphase»-Knöpfen. Die Analyse-Werkzeuge
(Grafiken, Live-Simulation, Monte-Carlo, Einflussfaktoren) und die Effektiven Werte sind hier
bewusst **nicht** mehr verlinkt sie laufen ausschliesslich über ihre eigenen Menüpunkte
([3.10](#310-navigation-auf-plan-ebene-und-gespeicherte-analysen)), damit es je Funktion genau
einen Ort gibt.
### 3.7.8 Tour und «Nächste Schritte»
Die **Tour** (`Tour.tsx`) startet einmalig beim ersten Öffnen eines Plans mit Phasen
(localStorage `fpt-tour-done`) und führt in bis zu sechs Schritten über Profil, Zeitachse,
Matrix, Übergänge, Cash und Analysen als Karte am unteren Rand plus pulsierender Rahmen um
das Ziel (`data-tour`-Attribute). Schritte ohne vorhandenes Ziel werden übersprungen; der
«Tour»-Knopf in der Werkzeugleiste startet sie jederzeit neu. Grenze: 9.24.
Die **Tour** (`Tour.tsx`) führt in bis zu sechs Schritten über Profil, Zeitachse, Matrix,
Übergänge, Cash und Analysen als Karte am unteren Rand plus pulsierender Rahmen um das Ziel
(`data-tour`-Attribute). Schritte ohne vorhandenes Ziel werden übersprungen; der «Tour»-Knopf
in der oberen Funktions-Leiste startet sie jederzeit neu. Grenze: 9.24.
Sie liegt seit 0.28 in `AppShell` (nicht mehr in `PlanView`), weil dort die Plan-Erstellung
zusammenläuft, und hat **zwei Auslöser**: Nach **jeder** Plan-Erstellung (Assistent,
Beispielplan, leerer Plan) startet sie **einmal** unabhängig davon, ob sie schon beendet
wurde (die Erfolgsmeldung «die Tour zeigt dir gleich …» hält damit ihr Versprechen). Zusätzlich
startet sie beim ersten Öffnen eines bestehenden Plans mit Phasen, solange sie noch nie beendet
wurde (localStorage `fpt-tour-done`). Ein aus dem DOM gelesenes Ziel setzt voraus, dass die
Szenario-Ansicht bereits gerendert ist; der Auslöser feuert deshalb erst, wenn das Szenario
geladen ist.
Die Karte **«Nächste Schritte»** über der Matrix leitet aus den vorhandenen Daten ab, was
sinnvollerweise als Nächstes ansteht offene Übergangs-Entscheide (mit Direktsprung in den
@@ -2455,6 +2491,7 @@ Zentral in `src/lib/constants.ts` geführt, weil sie sich periodisch durch Bunde
| `AHV_FULL_CONTRIBUTION_YEARS` | 44 | Volle Beitragsdauer (Rentenskala 44) |
| `AHV_REFERENCE_AGE` | 65 | Referenzalter ab hier fliesst die Rente, **unabhängig vom Pensionsalter** ([4.4.7](#447-referenzalter-die-rente-beginnt-mit-65-nicht-mit-der-pensionierung)) |
| `PILLAR_3A_MAX_ANNUAL` | 7'258 | Max. 3a-Beitrag/Jahr für PK-Versicherte (2026) |
| `PILLAR_3A_MAX_SELF_EMPLOYED` | 36'288 | Max. 3a-Beitrag/Jahr «grosse Säule 3a» (Selbstständige ohne PK). **2026-Wert zu verifizieren** (2025: 36'288) |
| `DEFAULT_PK_CONVERSION_RATE` | 6 % | Umwandlungssatz |
| `DEFAULT_CAPITAL_TAX_RATE` | 8 % | Kapitalbezugssteuer |
| `DEFAULT_PROPERTY_GAINS_TAX_RATE` | 20 % | Grundstückgewinnsteuer |
@@ -3338,6 +3375,7 @@ Referenz: `prisma/schema.prisma` Zeilen 46, `src/lib/elements.ts` Zeilen 48
| `startValue` | OTHER_ASSET, OTHER_DEBT | ≥ 0 |
| `expectedReturn` | PK, 3a, OTHER_ASSET | 50 bis 100 |
| `annualContribution` | PK, 3a, OTHER_ASSET | ≥ 0 |
| `selfEmployed3a` | PILLAR_3A «grosse Säule 3a» (Selbstständige ohne PK) → höhere Obergrenze | Boolean |
| `annualWithdrawal` | OTHER_ASSET | ≥ 0 |
| `additionalInvestment` | PK, 3a, OTHER_ASSET (ab Phase 2) | ≥ 0 |
| `purchasePrice` | REAL_ESTATE | ≥ 0 |