# 08 — Changelog Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum, Was, Warum, betroffene Dateien/Backups). ## 2026-08-17 (Nachtrag) — Ventilator statt Turbine, Speicher relativ zum Aufnahme-Vorrat **Zwei Korrekturen des Nutzers am selben Tag:** 1. **Icon soll wie ein Ventilator aussehen.** Der erste Entwurf (fünf schmale Tropfen ohne Gehäuse) las sich als Kleeblatt. Jetzt: vier gebogene Pinwheel-Flügel in einem feststehenden Gehäusering; nur die Flügelgruppe dreht. Die Form wurde vor dem Ausrollen ohne Browser geprüft — die Bézier-Segmente der Kandidaten wurden mit einem kleinen Python/Pillow-Skript abgetastet, rotiert und als PNG gerendert (`lab/preview_fan.py`), weil die Chrome-Anbindung an diesem Tag nicht verfügbar war. 2. **Prozentzahl soll den Aufnahme-Vorrat messen, nicht die Partition.** Neue Bezugsgröße: `100 % = freier Platz + Summe aller Audiodateien unter recordings/`, belegt = die Audiodateien. Das ist genau der Platz, den ein frisch aufgesetztes System dieser Ausbaustufe für Aufnahmen hätte; System, Modelle und `node_modules` fallen aus Zähler **und** Nenner heraus. Der `df`-Wert bleibt als Fußzeile („Partition gesamt“) in der Klappkarte. **Umsetzung:** `sysstat.js` scannt `recordings/` mit rekursivem `readdir` + `stat` (nur Audio-Endungen, kein `du`-Prozess) und puffert das Ergebnis 60 s; nur der allererste Aufruf wartet auf den Scan, sonst zeigte die Anzeige direkt nach dem Start einmal 0 %. Neue Felder `recBytes`, `recFiles`, `budgetBytes`, `partPct`; `usedPct` = `recBytes / budgetBytes`. `status()` ist dadurch `async` → die Route läuft jetzt über `wrap()`. GUI: Balken + Datei-Anzahl in der Karte, erklärende Fußnote, Fußzeile mit dem Partitionswert. Gegenprobe: Scan meldet 1 957 445 371 Bytes, `du -sb` sagt exakt dasselbe (103 Dateien, 52 mp3 + 51 wav) ⇒ 1,96 GB von 29,6 GB Vorrat = **6,6 %** (Partition dagegen 52 %). ## 2026-08-17 — Systemanzeige in der Kopfzeile: Speicherring + Turbine **Wunsch:** zwischen dem Bluetooth-Tab und dem Hell/Dunkel-Schalter eine Anzeige, die den belegten Speicherplatz in Prozent zeigt, dazu ein zweites Symbol, das bei Mauskontakt CPU-Last, RAM und Temperatur aufklappt — als Turbine, die sich mit steigender Last schneller dreht. **Umsetzung:** - Neues Modul `sysstat.js` mit `status()`: Platte über `fs.statfsSync` (gemessen wird die Partition mit den Aufnahmen, Prozentwert wie `df` inklusive root-Reserve), RAM aus `/proc/meminfo` (`MemAvailable`, nicht `MemFree`), CPU-% aus der Differenz zweier `/proc/stat`-Messpunkte, Temperatur als wärmste SoC-/Core-/Center-/GPU-/NPU-Zone (WLAN-Zone `iwlwifi_1` ausgeschlossen). Eigener 2-s-Ticker (`unref()`), sonst wäre die erste CPU-Antwort nach jedem Start 0 %. - `GET /api/sysstat` in `server.js` — ohne `wrap()`/Warteschlange, weil es reine `/proc`-Lesevorgänge sind und die Abfrage keinen Prozess startet. - GUI (`web/src/main.js`): `sysmonMarkup()` zeichnet beide Chips, `paintSysmon()` schreibt die Werte ins DOM, `tickSysmon()` pollt alle 5 s (im Hintergrund-Tab ausgesetzt). Ein einziger Ticker für alle `render()`-Läufe; direkt nach `render()` werden die letzten Werte zurückgemalt, weil `render()` die Kopfzeile komplett neu schreibt und die Chips sonst bis zum nächsten Poll leer stünden. - Turbine: fünf Flügel als SVG-Pfade, Drehdauer per CSS-Variable `--spin`. Die Dauer läuft bewusst in **Stufen** (7 s Leerlauf … 0,35 s ab 90 %) — eine stufenlos nachgeführte `animation-duration` setzt die laufende Drehung bei jedem Poll neu an und ruckt sichtbar. `prefers-reduced-motion` stoppt sie. - Speicherring: `` (Umfang exakt 100) ⇒ `stroke-dasharray` = Prozentwert. - Ampel: grün < 75 %, gelb ab 75 %, rot ab 90 %; bei der Turbine zählt der schlimmste Wert aus CPU, RAM und Temperatur (45 °C → 0 %, 90 °C → 100 %). - Karten öffnen rein über CSS (`.sys-chip:hover .sys-pop`, zusätzlich `:focus-within` für Tastatur) — kein Handler, der einen `render()`-Durchlauf überleben müsste. **Dateien:** neu `app/sysstat.js`; geändert `app/server.js` (`.bak-sysmon`), `app/web/src/main.js` (`.bak-sysmon`), `app/web/src/style.css` (`.bak-sysmon`). Vite-Build + `systemctl restart callrec` (vorher geprüft: keine Analyse `pending`). Verifiziert: `/api/sysstat` liefert 52,4 % Platte (`df` sagt 53 %), 46 °C, 8 Kerne. Doku: Kapitel 07. ## 2026-08-17 — Kontext zum Gespräch: ein Freitextfeld nach dem Stop **Wunsch:** nach dem Beenden einer Aufnahme die Möglichkeit haben, Kontext zum gerade geführten Call/Meeting zu ergänzen — das verbessert die Analyse nach dem Transkript erheblich, weil das Modell Personen, Firmen, Anlass und Eigennamen nicht mehr aus fehlerbehaftetem ASR-Text erraten muss. **Entscheidung des Nutzers: EIN Freitextfeld, kein Formular.** Ein Vorschlag mit Teilnehmer-, Anlass-, Projekt- und Begriffsfeldern wurde ausdrücklich verworfen: die Box benutzen später auch technisch nicht versierte Menschen, und „Kontext hinzufügen“ ist eine Hürde weniger als vier Pflichtfelder. Geführt wird über den Platzhaltertext, die Struktur (Namen, Begriffe) zieht sich die Analyse selbst aus der Prosa. **Umsetzung:** - Neue Spalten `recordings.context_note` / `context_note_at` (`ensureSchema`), `db.setContextNote()` (Deckel 4 000 Zeichen, leerer Text löscht) und `db.getContextNote()`. - `PATCH /api/recordings/:id/context`. Die Antwort enthält `applies` und sagt damit **ehrlich**, ob die Notiz die laufende Verarbeitung noch erreicht: `auto` (wird noch transkribiert → fließt sicher ein), `running` (Analyse läuft schon, kann sie verpasst haben), `reanalyze` (fertig → nur ein neuer Lauf nutzt sie; die GUI fragt dann nach und startet `reanalyze-llm`). - `analyzeRecording()` liest die Notiz **erst kurz vor dem LLM-Aufruf** (`db.getContextNote`), nicht aus der Zeile vom Anfang: Sprecher-Erkennung und Transkription davor brauchen Minuten, und was der Nutzer in dieser Zeit tippt, soll noch mitzählen. - `llm.js`: neuer `contextBlock()` — die Notiz kommt in einem eigenen `"""`-Block mit der Auflage, sie **nur** für Schreibweisen, Zuordnung, Anlass und Gewichtung zu nutzen und daraus keine Aussagen, Ergebnisse oder Aufgaben abzuleiten. Eingespeist in alle Pfade: `analyzeSingle` (`PROMPT_CLEAN`, `PROMPT_CLEAN_LABELED`, `PROMPT_JSON`), `analyzeWhole` (`PROMPT_WHOLE_ANALYZE`) und `analyzeLong` (jeder Chunk-Prompt + Titel- und Überblick-Mikro-Call). Zwei Deckel: 1 200 Zeichen für die Prompts, die die Notiz einmal sehen, 600 für die Chunk-Prompts (dort landet sie in **jedem** Chunk von 3 200 Zeichen). Auf dem letzten von drei Chunk-Versuchen fällt sie mit dem `hint` weg. - GUI: Karte „📝 Kontext zum Gespräch“ über den Modulen der Detailansicht. Nach dem Stop steht sie offen und fokussiert (`state.ctxPromptFor`), sonst eingeklappt mit dem gespeicherten Text und „✏️ Bearbeiten“. Sie zeichnet sich **selbst** neu statt über `openRecording()` — ein Neuaufbau der Detailansicht setzt den `