M20 aus Phase 2. Die Abarbeitungsansicht fragte alle zehn Sekunden nach dem Zustand — jetzt kommt die Änderung sofort.
Gemessen: 485 ms beim Zuschauer (am Handy 487 ms), vorher bis zu zehn Sekunden. Zwanzig offene Laschen kosten nichts mehr statt zwanzig Abfragen je zehn Sekunden.
Wie es gebaut ist
Das Ereignis kommt aus der Datenbank. Vier Trigger schicken NOTIFY lauf_geaendert, '<laufId>', eine LISTEN-Verbindung je Prozess hört zu, der Route Handler schiebt weiter. Aus demselben Grund, aus dem auch die Regeln dort stehen: ein Trigger sieht auch den Seed, eine Migration und die Reparatur per psql. Und NOTIFY erreicht jede Instanz der Anwendung, ohne einen zweiten Dienst wie Redis.
Ein Handler für beide Wege, unterschieden allein am Accept-Kopf, den EventSource von sich aus schickt. Damit ist „das Polling bleibt als Rückfall, nicht als zweite Wahrheit“ keine Absichtserklärung, sondern Bauart: dieselbe Rechteprüfung, dasselbe Lesemodell, dieselbe Nutzlast. Ein zweiter Endpunkt wäre eine zweite Stelle, an der jemand rechtErforderlich vergessen kann.
Die Obergrenze braucht keinen Sonderfall. Ist SSE_MAX_STROEME (200) erreicht, antwortet der Handler gewöhnlich mit JSON; EventSource meldet darauf einen Fehler und versucht es nicht wieder, also fällt der Client von sich aus auf Polling zurück.
Keine Verbindung aus dem Prisma-Pool. Ein Pool gibt Verbindungen zurück und verteilt sie neu; ein LISTEN darauf gilt danach für eine fremde Anfrage oder für niemanden.
Bei versteckter Lasche wird der Strom geschlossen, nicht bloß ignoriert — sonst belegte jede Hintergrundlasche genau die Verbindung, die die Obergrenze schützen soll.
Was das Issue falsch vorgeschlagen hatte
Es nannte Trigger auf Schrittergebnis und Lauf. laufZustand trägt aber auch die Zahl der Kommentare, und seit #52 gibt es Anhänge. Das Polling verglich alle zehn Sekunden den ganzen Stand und sah sie damit; ein Strom sieht nur, was gemeldet wird.
Mit den zwei Triggern aus dem Issue wäre der Kommentar des Kollegen nie angekommen — und niemandem wäre etwas aufgefallen, weil die Seite aussieht wie immer. Das ist derselbe Fehlertyp wie in #52: eine Zusage, die niemand prüft.
Jetzt melden vier Tabellen, laufZustand trägt anzahlAnhaenge (ein Ereignis ohne Entsprechung im Zustand löst nichts aus), und beide Tests zählen namentlich auf, was dazugehört. Gegenprobe gefahren: ohne die Trigger auf Kommentar und Anhang wird tests/lauf-ereignisse.test.ts rot.
client_max_body_size 8m bei ANHANG_MAX_MB=10: ein 10-MB-Foto wäre an nginx mit „413 Request Entity Too Large“ gescheitert, und die freundliche Meldung der Anwendung wäre nie zum Zuge gekommen. Jetzt 12m, mit dem Hinweis, dass beide Werte zusammengehören.
Der Kommentar am proxy_read_timeout beschrieb noch das alte Polling. Jetzt steht dort, warum der Wert nicht unter 30 s fallen darf: sonst reißt der Strom zwischen zwei Herzschlägen.
Gefunden beim Schreiben der Doku, nicht von einem Test — Dateien, die ein Dritter liest, prüft hier niemand.
1266 Vitest-Tests grün (vorher 1247), davon 18 neue in tests/lauf-ereignisse.test.ts
4 Playwright-Abläufe in tests/e2e/live.spec.ts, Desktop und Handy: zwei Browser-Kontexte am selben Lauf, und für den Rückfall wird dem Zuschauer EventSource vor dem Laden weggenommen (dauert dann erwartungsgemäß ~13 s)
Der Herzschlag echt über 70 Sekunden gefahren, nicht nur mit verkürztem Intervall im Test: readyState blieb OPEN, ein einziges Zustands-Ereignis, kein Fehler, kein Neuaufbau
Alle fünf Akzeptanzkriterien aus docs/issues/033-sse-live.md sind damit belegt.
M20 aus Phase 2. Die Abarbeitungsansicht fragte alle zehn Sekunden nach dem Zustand — jetzt kommt die Änderung sofort.
**Gemessen: 485 ms** beim Zuschauer (am Handy 487 ms), vorher bis zu zehn Sekunden. Zwanzig offene Laschen kosten nichts mehr statt zwanzig Abfragen je zehn Sekunden.
## Wie es gebaut ist
- **Das Ereignis kommt aus der Datenbank.** Vier Trigger schicken `NOTIFY lauf_geaendert, '<laufId>'`, eine LISTEN-Verbindung je Prozess hört zu, der Route Handler schiebt weiter. Aus demselben Grund, aus dem auch die Regeln dort stehen: ein Trigger sieht auch den Seed, eine Migration und die Reparatur per `psql`. Und `NOTIFY` erreicht jede Instanz der Anwendung, ohne einen zweiten Dienst wie Redis.
- **Ein Handler für beide Wege**, unterschieden allein am `Accept`-Kopf, den `EventSource` von sich aus schickt. Damit ist „das Polling bleibt als Rückfall, nicht als zweite Wahrheit“ keine Absichtserklärung, sondern Bauart: dieselbe Rechteprüfung, dasselbe Lesemodell, dieselbe Nutzlast. Ein zweiter Endpunkt wäre eine zweite Stelle, an der jemand `rechtErforderlich` vergessen kann.
- **Die Obergrenze braucht keinen Sonderfall.** Ist `SSE_MAX_STROEME` (200) erreicht, antwortet der Handler gewöhnlich mit JSON; `EventSource` meldet darauf einen Fehler und versucht es nicht wieder, also fällt der Client von sich aus auf Polling zurück.
- **Keine Verbindung aus dem Prisma-Pool.** Ein Pool gibt Verbindungen zurück und verteilt sie neu; ein `LISTEN` darauf gilt danach für eine fremde Anfrage oder für niemanden.
- Bei versteckter Lasche wird der Strom **geschlossen**, nicht bloß ignoriert — sonst belegte jede Hintergrundlasche genau die Verbindung, die die Obergrenze schützen soll.
## Was das Issue falsch vorgeschlagen hatte
Es nannte Trigger auf `Schrittergebnis` und `Lauf`. `laufZustand` trägt aber auch die Zahl der **Kommentare**, und seit #52 gibt es **Anhänge**. Das Polling verglich alle zehn Sekunden den ganzen Stand und sah sie damit; ein Strom sieht **nur, was gemeldet wird**.
Mit den zwei Triggern aus dem Issue wäre der Kommentar des Kollegen **nie** angekommen — und niemandem wäre etwas aufgefallen, weil die Seite aussieht wie immer. Das ist derselbe Fehlertyp wie in #52: eine Zusage, die niemand prüft.
Jetzt melden vier Tabellen, `laufZustand` trägt `anzahlAnhaenge` (ein Ereignis ohne Entsprechung im Zustand löst nichts aus), und beide Tests zählen namentlich auf, was dazugehört. **Gegenprobe gefahren:** ohne die Trigger auf `Kommentar` und `Anhang` wird `tests/lauf-ereignisse.test.ts` rot.
## Zwei Fehler in der Proxy-Doku, beide aus #52
- `client_max_body_size 8m` bei `ANHANG_MAX_MB=10`: ein 10-MB-Foto wäre an nginx mit „413 Request Entity Too Large“ gescheitert, und die freundliche Meldung der Anwendung wäre nie zum Zuge gekommen. Jetzt 12m, mit dem Hinweis, dass beide Werte zusammengehören.
- Der Kommentar am `proxy_read_timeout` beschrieb noch das alte Polling. Jetzt steht dort, warum der Wert nicht unter 30 s fallen darf: sonst reißt der Strom zwischen zwei Herzschlägen.
Gefunden beim Schreiben der Doku, nicht von einem Test — Dateien, die ein Dritter liest, prüft hier niemand.
## Geprüft
- `pnpm lint`, `typecheck`, `format:pruefen`, `pruefe:doku` grün
- **1266 Vitest-Tests** grün (vorher 1247), davon 18 neue in `tests/lauf-ereignisse.test.ts`
- **4 Playwright-Abläufe** in `tests/e2e/live.spec.ts`, Desktop und Handy: zwei Browser-Kontexte am selben Lauf, und für den Rückfall wird dem Zuschauer `EventSource` vor dem Laden weggenommen (dauert dann erwartungsgemäß ~13 s)
- **Der Herzschlag echt über 70 Sekunden gefahren**, nicht nur mit verkürztem Intervall im Test: `readyState` blieb `OPEN`, ein einziges Zustands-Ereignis, kein Fehler, kein Neuaufbau
Alle fünf Akzeptanzkriterien aus `docs/issues/033-sse-live.md` sind damit belegt.
Closes #53
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Die Abarbeitungsansicht fragte alle zehn Sekunden nach dem Zustand. Jetzt
melden vier Datenbank-Trigger `NOTIFY lauf_geaendert, '<laufId>'`, eine
LISTEN-Verbindung je Prozess hoert zu, und der Route Handler schiebt
weiter. Gemessen: der Haken des Kollegen ist nach 485 ms beim Zuschauer
(am Handy 487 ms), vorher bis zu zehn Sekunden. Zwanzig offene Laschen
kosten nichts mehr statt zwanzig Abfragen je zehn Sekunden.
Das Ereignis kommt aus der Datenbank und nicht aus dem Dienst, aus
demselben Grund, aus dem auch die Regeln dort stehen: ein Trigger sieht
auch den Seed, eine Migration und die Reparatur per psql. Und NOTIFY
erreicht jede Instanz der Anwendung, ohne einen zweiten Dienst.
Welche Tabellen melden, haengt an dem, was `laufZustand` liefert — nicht
daran, was das Issue vorgeschlagen hat. Mit Triggern nur auf
Schrittergebnis und Lauf waere der Strom ein **Rueckschritt** gewesen:
das Polling verglich den ganzen Stand und sah deshalb auch Kommentare,
ein Strom sieht nur, was gemeldet wird. Der Kommentar des Kollegen waere
nie angekommen, und die Seite haette ausgesehen wie immer. Es melden
jetzt vier Tabellen, und `laufZustand` traegt `anzahlAnhaenge` — ein
Ereignis ohne Entsprechung im Zustand loest nichts aus.
Das Polling bleibt als Rueckfall an **derselben** Adresse, unterschieden
nur am Accept-Kopf: eine Rechtepruefung, ein Lesemodell, eine Nutzlast.
Ein zweiter Endpunkt waere eine zweite Stelle, an der jemand
rechtErforderlich vergessen kann. Bei erreichter Obergrenze antwortet
der Handler gewoehnlich mit JSON; EventSource meldet darauf einen Fehler
und versucht es nicht wieder, also faellt der Client von sich aus
zurueck — ohne Sonderfall.
Nebenbei zwei Fehler in docs/betrieb/reverse-proxy.md behoben, beide aus
#52: `client_max_body_size 8m` bei `ANHANG_MAX_MB=10` haette ein
10-MB-Foto mit 413 abgewiesen, bevor die eigene Meldung zum Zuge kommt.
Closes#53
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
M20 aus Phase 2. Die Abarbeitungsansicht fragte alle zehn Sekunden nach dem Zustand — jetzt kommt die Änderung sofort.
Gemessen: 485 ms beim Zuschauer (am Handy 487 ms), vorher bis zu zehn Sekunden. Zwanzig offene Laschen kosten nichts mehr statt zwanzig Abfragen je zehn Sekunden.
Wie es gebaut ist
NOTIFY lauf_geaendert, '<laufId>', eine LISTEN-Verbindung je Prozess hört zu, der Route Handler schiebt weiter. Aus demselben Grund, aus dem auch die Regeln dort stehen: ein Trigger sieht auch den Seed, eine Migration und die Reparatur perpsql. UndNOTIFYerreicht jede Instanz der Anwendung, ohne einen zweiten Dienst wie Redis.Accept-Kopf, denEventSourcevon sich aus schickt. Damit ist „das Polling bleibt als Rückfall, nicht als zweite Wahrheit“ keine Absichtserklärung, sondern Bauart: dieselbe Rechteprüfung, dasselbe Lesemodell, dieselbe Nutzlast. Ein zweiter Endpunkt wäre eine zweite Stelle, an der jemandrechtErforderlichvergessen kann.SSE_MAX_STROEME(200) erreicht, antwortet der Handler gewöhnlich mit JSON;EventSourcemeldet darauf einen Fehler und versucht es nicht wieder, also fällt der Client von sich aus auf Polling zurück.LISTENdarauf gilt danach für eine fremde Anfrage oder für niemanden.Was das Issue falsch vorgeschlagen hatte
Es nannte Trigger auf
SchrittergebnisundLauf.laufZustandträgt aber auch die Zahl der Kommentare, und seit #52 gibt es Anhänge. Das Polling verglich alle zehn Sekunden den ganzen Stand und sah sie damit; ein Strom sieht nur, was gemeldet wird.Mit den zwei Triggern aus dem Issue wäre der Kommentar des Kollegen nie angekommen — und niemandem wäre etwas aufgefallen, weil die Seite aussieht wie immer. Das ist derselbe Fehlertyp wie in #52: eine Zusage, die niemand prüft.
Jetzt melden vier Tabellen,
laufZustandträgtanzahlAnhaenge(ein Ereignis ohne Entsprechung im Zustand löst nichts aus), und beide Tests zählen namentlich auf, was dazugehört. Gegenprobe gefahren: ohne die Trigger aufKommentarundAnhangwirdtests/lauf-ereignisse.test.tsrot.Zwei Fehler in der Proxy-Doku, beide aus #52
client_max_body_size 8mbeiANHANG_MAX_MB=10: ein 10-MB-Foto wäre an nginx mit „413 Request Entity Too Large“ gescheitert, und die freundliche Meldung der Anwendung wäre nie zum Zuge gekommen. Jetzt 12m, mit dem Hinweis, dass beide Werte zusammengehören.proxy_read_timeoutbeschrieb noch das alte Polling. Jetzt steht dort, warum der Wert nicht unter 30 s fallen darf: sonst reißt der Strom zwischen zwei Herzschlägen.Gefunden beim Schreiben der Doku, nicht von einem Test — Dateien, die ein Dritter liest, prüft hier niemand.
Geprüft
pnpm lint,typecheck,format:pruefen,pruefe:dokugrüntests/lauf-ereignisse.test.tstests/e2e/live.spec.ts, Desktop und Handy: zwei Browser-Kontexte am selben Lauf, und für den Rückfall wird dem ZuschauerEventSourcevor dem Laden weggenommen (dauert dann erwartungsgemäß ~13 s)readyStatebliebOPEN, ein einziges Zustands-Ereignis, kein Fehler, kein NeuaufbauAlle fünf Akzeptanzkriterien aus
docs/issues/033-sse-live.mdsind damit belegt.Closes #53
🤖 Generated with Claude Code
4aa79c93ffto1be73b266cPull request closed