4791dccf93
Deploy App / deploy (push) Successful in 57s
Roadmap Nr. 43 (Detailansichten) und Nr. 41 (Berechnungslogiken offenlegen).
Systemparameter-Ansicht:
- SYSTEM_PARAMETERS in constants.ts: Wert, Bedeutung, Herleitung, Quelle,
Stand -- aus derselben Datei, aus der gerechnet wird
Detailansichten (nur lesen, per Expand-Icon):
- Element: Verlauf ueber ALLE Planjahre (neu ElementPhaseComputed.yearly),
bei Immobilien Verkehrswert/Restschuld/Eigenkapital getrennt
- Phase: Vermoegensaufteilung + zwei Wasserfaelle
Zwei getrennte Wasserfaelle (WealthBridge / CashBridge):
- Sparraten, Amortisationen und Investitionen sind UMBUCHUNGEN und erscheinen
nur im Cash-Wasserfall -- als Vermoegensabgang gezeichnet wuerden sie einen
Verlust vortaeuschen, den es nicht gibt
- PK-Beitraege dagegen sind ein echter Vermoegenszugang (belasten kein Cash),
Verrentung ein echter Abgang (Kapital verlaesst die Bilanz)
- residual als Kontrollgroesse fuer die Vollstaendigkeit der Zerlegung
Rechenweg-Protokoll, vollstaendige Abdeckung:
- computePlan(plan, sample?, { explain }) protokolliert die Schritte, die es
ohnehin ausfuehrt -- die Erklaerung IST die Rechnung, statt einer zweiten
Formel-Implementierung im UI, die still abdriften koennte
- standardmaessig aus (Monte Carlo bleibt unberuehrt)
- Arithmetik nicht umgestellt: Zwischengroessen werden als Differenz
abgeleitet, damit die 43 Golden Tests bitgleich bleiben
- jeder Trace verlinkt in die SPEZIFIKATION; ein Test prueft gegen die echte
Datei, dass alle Verweise eine existierende Ueberschrift treffen
SPEZIFIKATION auf 0.11: neue Kapitel 3.6.7, 3.6.8, 4.14, 9.20, 9.21.
12 Tests ergaenzt (80 -> 92). Keine DB-Aenderung.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
59 lines
2.1 KiB
TypeScript
59 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import ReactMarkdown from "react-markdown";
|
|
import remarkGfm from "remark-gfm";
|
|
import rehypeSlug from "rehype-slug";
|
|
import { FileText } from "lucide-react";
|
|
import { api } from "@/lib/api-client";
|
|
|
|
// Rendert SPEZIFIKATION.md (via /api/spec) als lesbares Dokument. Das Styling laeuft ueber
|
|
// die Klasse .md-doc in globals.css und folgt damit dem gewaehlten Farbschema.
|
|
export function SpecView({ anchor }: { anchor?: string | null }) {
|
|
const [markdown, setMarkdown] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
api
|
|
.get<{ markdown: string }>("/api/spec")
|
|
.then((data) => setMarkdown(data.markdown))
|
|
.catch((e) => setError(e instanceof Error ? e.message : "Laden fehlgeschlagen."));
|
|
}, []);
|
|
|
|
// Sprungmarke aus einem Rechenweg: erst nach dem Rendern des Markdowns existiert die
|
|
// Ueberschrift mit der von rehype-slug erzeugten id.
|
|
useEffect(() => {
|
|
if (!anchor || markdown === null) return;
|
|
const el = document.getElementById(anchor);
|
|
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
}, [anchor, markdown]);
|
|
|
|
if (error) return <p className="text-sm text-danger">{error}</p>;
|
|
if (markdown === null) return <p className="text-sm text-muted">Laedt…</p>;
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<div className="flex items-center gap-2">
|
|
<FileText className="h-5 w-5 text-accent" />
|
|
<h2 className="text-lg font-semibold text-fg">Spezifikation</h2>
|
|
</div>
|
|
<article className="md-doc rounded-xl border border-border bg-surface p-6 shadow-sm lg:p-8">
|
|
<ReactMarkdown
|
|
remarkPlugins={[remarkGfm]}
|
|
rehypePlugins={[rehypeSlug]}
|
|
components={{
|
|
// Breite Tabellen scrollen in ihrem eigenen Container statt die Seite zu dehnen.
|
|
table: ({ children, ...props }) => (
|
|
<div className="md-table-wrap">
|
|
<table {...props}>{children}</table>
|
|
</div>
|
|
),
|
|
}}
|
|
>
|
|
{markdown}
|
|
</ReactMarkdown>
|
|
</article>
|
|
</div>
|
|
);
|
|
}
|