Vier Befehle erzeugen Dokumente zum Weitergeben: loc (HTML · PDF · PNG),
pagespeed (HTML · PDF), repo (HTML) und die Zeiterfassung
(HTML ×2 + CSV). Sie teilen sich seit v0.143.0 ein einziges Stylesheet und ein
einziges Dokument-Gerüst: core/rust-lib/src/report_style.rs.
Vorher trugen fünf Dokumente vier handgeschriebene Stylesheets, zwei davon dunkel. Das ist die Art Drift, die niemand bemerkt, bis zwei Reports nebeneinander liegen.
| Regel | Warum |
|---|---|
| Hell, druckorientiert | Ein Report ist etwas, das man druckt, anhängt oder einem Kunden gibt. Ein dunkles PDF verbrennt Toner und sieht auf Papier kaputt aus. |
| A4, nicht Letter | Die Leser sind hier. Letter ließ auf jeder gedruckten Seite einen Streifen stehen. |
| Farbe nur, wo sie Daten trägt | Kein dekorativer Akzent. Die Lighthouse-Bänder und die Serienfarben bedeuten etwas, der Rest ist Tinte, gedämpfte Tinte, Haarlinie. Das hält eine dichte Seite ruhig. |
| Zahlen sind eine Spalte | tabular-nums überall, rechtsbündig — Ziffer unter Ziffer. Der größte Lesbarkeitsgewinn in einer Datenauswertung. |
| Data-Ink statt Rahmen | Haarlinien und Weißraum statt Kästen; keine senkrechten Linien, keine Zebrastreifen. |
| Anteil in der Zeile | Der Balken steht beim Namen, nicht in einer eigenen Spalte, zu der das Auge wandern muss. |
| Selbsttragend | Eingebettetes CSS, eingebettetes SVG, kein Skript, keine Netz-Anfrage. Ein Report muss offline, in einem kopflosen WebView und in drei Jahren gleich aussehen. |
report_style::css() // das Stylesheet
report_style::shell(kicker, title, subject, body, foot)
report_style::stats(&[Stat { label, value, unit }]) // Kennzahlen-Leiste
report_style::share_bar(&[(label, anteil, farbe)]) // Balken + Legende
report_style::name_cell(farbe, name, anteil) // Zeilen-Label mit Spur
report_style::series_color(name) // Serienfarbe
report_style::pct(anteil) // 0.118 -> "11,8 %"Klassen: .rp-text (linksbündige Textspalte), .rp-num (Zahl), .rp-dim
(gedämpft), .rp-lede (neutraler Erklärsatz), .rp-note (bernsteinfarbener
Warnkasten — nur für Vorbehalte), .rp-empty (nichts vorhanden).
Ein Report, der eigene Regeln braucht, hängt sie an: doc.replace("</style>", &format!("{EIGENES_CSS}\n</style>")). So bleibt die gemeinsame Basis
unangetastet.
Ohne diese Regel verwirft WebKit beim PDF-Rendern jeden Hintergrund. Verloren gehen dann Anteilsbalken, Band-Chips und Ringe — also genau die Teile, die die Aussage tragen. Der Report sieht nicht kaputt aus, er sieht leer aus, was schlimmer ist. Ein Test pinnt die Regel.
createPDFWithConfiguration: nil nimmt das Seitenrechteck aus den Bounds
der WebView. Der Rahmen in md_to_pdf.rs
ist also das Papier: 794 × 1123 = A4 bei 96 dpi. @page { size: A4 } allein
hätte nichts geändert.
Ein Hash direkt auf 0–360° ist beständig, aber trennt nicht: zwei Sprachen im selben Report kamen als fast identische Türkistöne heraus. Jetzt bildet der Name deterministisch in eine kuratierte Zwölfer-Palette ab — Beständigkeit und Unterscheidbarkeit. Ein Test verbietet Dubletten und zu helle Einträge.
Das genügt, solange ein Report eine Textspalte hat — loc und repo haben
genau das, und die Zahlen sind die letzten Spalten. Die Zeiterfassung hat
mehrere (Projekt, App, Host, Tätigkeit); ohne .rp-text rutschten sie alle
nach rechts und sahen wie Zahlen aus.
th + th, td + td { padding-left: 18px }. Ohne sie stößt eine rechtsbündige
Zahlenspalte direkt an die nächste linksbündige Textspalte — gemessen:
2.5010:00:00–12:30:00. Bei loc/repo fällt das nie auf, weil dort die
Zahlen am Ende stehen. Das sieht man nur im gerenderten Bild, kein Test
hätte es gefunden.
Bernstein auf Creme mit Rahmen — für den inaccurate-Vorbehalt gebaut. Ein
neutraler Erklärsatz darin liest sich als Warnung; dafür gibt es .rp-lede.
format!(…).replace('.', ",") über die fertige Legendenzeile traf auch das
Label: aus Node.js wurde Node,js. Nur die Prozentzahl wird umgestellt.
thead { display: table-header-group } — eine Tabelle, die auf Seite 2 läuft,
darf ihren Kopf nicht verlieren. Dazu break-inside: avoid auf Abschnitten,
Kennzahlen-Leisten und Zeilen.
takeSnapshot fotografiert die Bounds. Bei Inhalt, der kürzer als der
Rahmen ist, klemmt scrollHeight auf die Viewport-Höhe (gemessen: 1200 für ein
~400-pt-Dokument) — und bei längerem Inhalt hätte dieselbe naive Messung den
Report abgeschnitten. Gemessen wird die Unterkante des Fußtexts.
| Befehl | HTML | PNG | Besonderheit | |
|---|---|---|---|---|
loc |
✅ | ✅ | ✅ | Sprachen-Tabelle mit Anteils-Spur je Zeile |
pagespeed |
✅ | ✅ | — | Desktop und Mobil nebeneinander in einem Dokument |
repo |
✅ | ✅ | — | Balkenreihen für Wochentag/Stunde/Monat |
| Zeiterfassung | ✅ | ✅ | — | zwei Dokumente (Projekt-Report, Timesheet) + CSV |
PDF überall, PNG nur wo das Dokument auf einen Blick passt. Ein loc-Report
ist eine Karte, die man in einen Chat zieht; ein Timesheet über eine Woche ist
mehrseitig — davon ein Bild zu machen hieße, es unlesbar zu falten.
Alle vier gehen durch einen Schreiber (commands::write_report): HTML/CSV
direkt auf die Platte, PDF/PNG durch WebKit. async
sein — WebKit besteht auf dem Hauptthread, und ein synchroner
#[tauri::command] läuft selbst dort und wartete auf sich selbst.
PNG ist bisher nur macOS — der Schnappschuss hängt an WKWebView; anderswo meldet der Befehl das ehrlich, statt eine leere Datei zu schreiben.
docs/disk.md·docs/repo.md·docs/timesheet.md- Befehls-Doku im Programm:
loc?,pagespeed?,repo?