fix(gui): Recorder-Zustand nach Neuladen vom Server holen

Die GUI hielt `recording`/`recStart`/`markers` nur im Browser-State und fragte
den Server nie danach. Nach jedem F5 stand der Recorder auf 00:00, obwohl
parecord weiterlief — und das Kopfband malte den Start- statt des Stop-Knopfes,
die laufende Aufnahme war aus der GUI also nicht mehr beendbar.

`rehydrateRecording()` liest nach dem ersten render() `GET /api/health` und
stellt Laufzeit, Namensmarker und Live-Karte wieder her; Fehler bleiben still.
Nebeneffekt: ein zweites Gerät sieht die laufende Aufnahme ebenfalls.

Doku: Kapitel 03 neuer Abschnitt + /api/health in der Endpunkt-Tabelle,
Changelog-Eintrag 2026-08-10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-10 13:18:11 +02:00
co-authored by Claude Opus 5
parent 2228c57522
commit 2be938c91b
3 changed files with 87 additions and 0 deletions
+36
View File
@@ -110,10 +110,46 @@ linear — bei konstanter Bitrate exakt.
30,3 MB. Der Altbestand wurde einmalig mit `lab/recode-cbr.sh` umgeschrieben (Quelle: die WAV, wo noch
vorhanden — sonst die MP3 selbst, dann ohne erneute Filter).
## Der Aufnahmezustand gehört dem Server (Reload-Sicherheit)
Wer aufnimmt, weiß nur `recorder.js` — dort hält `current` den laufenden `parecord`-Prozess, den WAV-Pfad,
den Startzeitpunkt und die Namensmarker. Die GUI ist reiner Betrachter: sie muss sich diesen Zustand beim
Laden **holen**, denn ihr eigener `state.recording` ist nach jedem `F5`, Tab-Wechsel des Browsers oder auf
einem zweiten Gerät wieder `false`.
```mermaid
flowchart TB
A["Seite lädt: render()"] --> B["rehydrateRecording()<br/>GET /api/health"]
B --> C{"recording?"}
C -- nein --> D["Recorder zeigt ●, 00:00 — korrekt"]
C -- ja --> E["state.recording = true<br/>recStart = startedAt (Server-Epoche)<br/>markers = Server-Marker"]
E --> F["render(): ■ Stop, laufende Zeit,<br/>Namensfeld, Marker-Chips, Live-Karte in der Liste"]
```
`GET /api/health` ist die einzige Quelle dafür und liefert genau das Nötige:
```json
{ "ok": true, "recording": true, "startedAt": 1786359542932, "source": "default",
"markers": [ { "t_ms": 8401, "name": "Markus Ufer" } ] }
```
- `recStart` kommt als **Server-Epoche**; der Timer rechnet `Date.now() - recStart`. Epoch-Werte sind
zeitzonenfrei (Board läuft auf UTC, Browser auf CEST) — nur eine echte Uhrabweichung würde verschieben.
- Fehlschlag ist absichtlich still: kein Toast, die GUI startet dann einfach im Normalzustand.
- Zweites Gerät bekommt dieselbe laufende Aufnahme angezeigt — der Zustand ist nicht mehr an einen Tab
gebunden.
⚠️ **Warum das mehr als Kosmetik ist:** ohne den Abgleich malt das Kopfband nach dem Neuladen den
Start-Knopf (●), der Stop-Knopf ist also unerreichbar, während `parecord` weiterschreibt. Ein Druck auf ●
läuft in `recorder.js` gegen `if (current) throw new Error('already recording')` — es gibt keinen
Datenverlust, aber auch keinen Weg mehr, die Aufnahme aus der GUI zu beenden. Notausgang ohne GUI bleibt
`curl -X POST localhost:3000/api/record/stop` (speichert regulär und startet die Transkription).
## API-Endpunkte (Auszug)
| Endpunkt | Zweck |
|---|---|
| `GET /api/health` | Läuft eine Aufnahme? (`recording`, `startedAt`, `source`, `markers`) — Grundlage der Reload-Wiederherstellung |
| `POST /api/recordings/:id/retranscribe` | Hängende/fehlgeschlagene Transkription neu starten |
| `POST /api/recordings/:id/reanalyze` `{numSpeakers?}` | Diarization **immer** neu (leer/0 = automatisch, Zahl = Anzahl gepinnt) + LLM neu |
| `POST /api/recordings/:id/reanalyze-llm` | Nur LLM neu, Diarization/Wörter bleiben |