Commit Graph
15 Commits
Author SHA1 Message Date
DogFatherGitandClaude Opus 5 e7cd1984d8 Ein Rollenname im ausgelieferten Code, die Seite zu breit, und drei Pruefungen, die den Umbau verschlafen hatten
Filipe: "falls es noch was gibt was fehlt oder nicht richtig funktioniert,
auf dem handy oder auf dem pc, oder als installiert, ich will dass du das
alles abcheckst und machst dass jede seite reibungslos klappt."

Gemessen wurde der ganze Bestand: 26 Pruefdateien einzeln, dazu drei neue
Messungen fuer Dinge, die keine Pruefung ansieht. Von 37 roten Meldungen
aus dem Nachtlauf sind nach dieser Runde die haelfte erledigt -- und die
Haelfte davon war gar nicht kaputt.

=== ECHTE FEHLER ===

1. EIN ROLLENNAME STAND IM AUSGELIEFERTEN QUELLTEXT.
   `aufgaben.js` sagte "Modis moechten das uebernehmen -- du
   entscheidest". Diese Datei bekommt JEDER, der die Seite oeffnet:
   jeder Manager, jeder Scout, jeder Creator im anderen Haus. Der
   verborgene Zugang haelt genau so lange, wie der Name dort nicht
   steht. Er stammte aus der Bewerbungsspalte von gestern -- einen Tag
   alt, gefunden von pruef-modi-wortleck. Jetzt: "Jemand moechte das
   uebernehmen". Wer es ist, steht ohnehin mit Namen auf den Karten
   darunter, und die sieht nur, wer sie sehen darf.

2. DIE SUPPORT-SEITE WAR AUF JEDER BREITE UNTER 1440 px ZU BREIT --
   13 px auf dem Handy, 41 px auf dem Tablet quer, 30 px auf dem
   Laptop. Schuld war das weiche Licht, das ich in der Nacht eingebaut
   habe: `inset: -8% -4% 0`. Die vier Prozent rechts sahen nach einer
   Kleinigkeit aus; ein absolut gesetztes Element zaehlt aber zur
   Scrollbreite, auch mit `pointer-events: none` und `z-index: -1`.

   `pruef-breiten` hat es gemeldet und konnte nicht sagen, WAS es ist
   ("13px Ueberstand []") -- ein Pseudoelement hat keine Box im DOM.
   Gefunden hat es eine neue Messung, die JEDES echte Element abfragt:
   keines ragte heraus, und genau das war der Hinweis.
   Jetzt: 0 px auf allen neun Breiten, 38 Seiten, 84 252 Elemente.

3. EIN AUFGABENLINK IM REPORT WAR 27 x 18 px GROSS. Mit der Maus gut,
   mit dem Daumen nicht -- eine Fingerkuppe ist rund 45 px breit.
   Jetzt fuellt der Link die Zeile und ist 44 px hoch, aber nur auf
   schmalen Bildschirmen und an groben Zeigegeraeten. Die zweite
   Bedingung habe ich beim ersten Anlauf vergessen, und der Link blieb
   27 x 21 -- eine Regel, die nur unter idealen Bedingungen greift,
   hilft niemandem auf dem halben Weg dorthin.

4. EINE SEITE LUD DAS FALSCHE MANIFEST. `anruf-probe.html` verwies
   fest auf `crew.webmanifest`; die Seite ist aber fuer ALLE Rollen
   offen und damit auf beiden Adressen erreichbar. Wer sie im
   Agenturhaus oeffnet und die App installiert, bekam "DogFather
   Universe" mit dem Crew-Symbol auf den Startbildschirm. Alle anderen
   38 Seiten machen es richtig: `app.webmanifest`, und die Adresse
   biegt es um.

5. DER KNOPF "+ GIF HINZUFUEGEN" WAR 40 px HOCH, und der Kommentar
   daneben nannte das "die Hausgroesse". Die Hausgroesse sind 44 --
   fuenfmal in derselben Datei so begruendet. Die erste Reparatur
   griff nicht: Die Fingerregel stand 400 Zeilen VOR der Grundregel,
   und bei gleicher Staerke gewinnt die spaetere. Jetzt steht sie
   direkt dahinter, und der Knopf misst 133 x 44 am Finger, 133 x 40
   an der Maus.

   KEINE PRUEFUNG KONNTE DAS FINDEN: Die GIF-Tafel ist zu, solange
   niemand sie aufmacht, und alle Rundgaenge messen, was auf dem
   Bildschirm steht. Dafuer gibt es jetzt server/mess-gifs-handy.mjs.

6. ZWEI KLEINIGKEITEN AUS DER NACHT: eine Fehlerkennung ohne Satz
   (`unbekannter_punkt`) und eine Stelle in support.js, die den
   Serverfehler roh anzeigte statt durch `fehlerText` -- die zwei
   anderen Stellen derselben Datei machen es richtig. So entsteht eine
   Ausnahme: nicht aus Absicht, sondern weil man die Hausregel beim
   Neuschreiben nicht danebenliegen hatte.

7. TOTES CSS (.e-leerwahl, vier Regeln). Der Leerkasten ist am
   24.09. auf Filipes Wunsch wieder verschwunden, sein Stil blieb
   einen Tag laenger stehen.

=== ROT, ABER NICHT KAPUTT ===

Drei Pruefungen haben den Umbau vom 24.09. nicht mitbekommen:

  pruef-anruf meldete 31 Fehler und "ein Gespraech entsteht (403)".
  Sie meldete DogFather auf der AGENTUR-Adresse an und liess ihn dann
  die rechte Hand anrufen -- die es dort seit der Haustrennung nicht
  gibt. Der Server hatte recht. Jetzt telefoniert Team Dogi auf crew.,
  und der Creator bleibt auf workspace. -- seine Gegenprobe ist damit
  sogar schaerfer als vorher (ein Fremder aus dem ANDEREN Haus).
  96 -> 127 Pruefungen, 0 Fehler.

  pruef-crew-wand-bild erwartete vier Rollen auf der Zugangswand. Es
  sind fuenf, seit die linke Hand am 21.09. dazukam. Die Zeile stand
  unter einem Kommentar, der wortwoertlich vor festen Namen in
  Pruefungen warnt ("eine Zeitbombe mit Datum") -- und war selbst
  einer. Jetzt leitet sie die Rollen aus `rollenImHaus("crew")` ab,
  derselben Quelle, aus der der Server die Wand baut.

  pruef-entwicklung-kacheln rechnete noch mit "x von 68". Seit
  Filipes Wunsch ("es sollen nur die menge angezeigt werden die wir
  zutragen") ist das Ganze das, was jemandem zugetragen ist. Sie baut
  jetzt BEIDE Faelle -- eine Person mit vier zugetragenen Punkten und
  eine ohne -- und misst Ring, Bogen, Zeile und Vorleseschild gegen
  die Zuteilung. Dazu eine Gegenprobe mit einer Einschaetzung auf
  einem NICHT zugetragenen Punkt: zaehlt die Uebersicht sie mit,
  stuende dort 2 statt 1. 31 -> 34 Pruefungen, 0 Fehler.

=== WAS DIE BILDSCHAU ANGEHT ===

Meine eigene Messung meldete erst "Mitte 174 von 640" -- die Schau sah
kaputt aus. Sie war es nicht: `querySelector("img")` nahm das
Husky-Zeichen in der Kopfzeile statt des Bildes. Nachgemessen am
richtigen Element steht es auf 195 von 195 (Handy) und 640 von 640
(Rechner), und ein GIF oeffnet sich als GIF. Ein Messfehler, der wie
ein Befund aussieht, kostet mehr Zeit als gar keine Messung -- deshalb
steht die Begruendung jetzt im Quelltext der Messung.

GEMESSEN: pruef-breiten (9 Breiten, 38 Seiten, 84 252 Elemente),
pruef-support 63/0, pruef-gifs 17/0, pruef-chat 63/0, pruef-chat-optik
62/0, pruef-tippziele 11/0, pruef-css-klassen, pruef-struktur,
pruef-meldungen, pruef-modi-wortleck, pruef-crew-adresse,
pruef-installieren, pruef-aufgabenbrett, pruef-entwicklung -- alle 0
Fehler.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-25 12:31:21 +02:00
DogFatherGitandClaude Opus 5 6e46a08543 Portnummern werden abgeleitet, nicht mehr vergeben
Gemessen: 165 Pruefdateien, 129 verschiedene Nummern -- NEUNZEHN
doppelt, vier davon dreifach. Niemand hatte das gewollt; jede neue
Pruefung wurde von einer vorhandenen abgeschrieben, und die Nummer kam
mit. Meine eigene Notiz sagte "sieben" -- auch eine Bestandsliste
altert.

Der Waechter faengt den Schaden ab, aber er kann nur melden, was schon
passiert ist: Zwei Pruefungen mit derselben Nummer koennen nie
gleichzeitig laufen, und ein liegengebliebener Prozess der einen laesst
die andere abbrechen mit einer Meldung, die wie ein Befund aussieht.
Genau das ist mir am 20.09. zweimal passiert.

Jetzt leitet jede Datei ihre Nummer aus ihrer STELLE IM ALPHABET ab
(eigenerPort in helfer-port.mjs), zwei je Datei. Nicht ueber eine
Pruefsumme: Bei 165 Namen in 4900 Nummern waeren nach dem
Geburtstagsproblem rund DREI Zusammenstoesse zu erwarten -- ein Hash
tauscht eine sichtbare Doppelung gegen eine unsichtbare. Die Stelle im
Alphabet ist eindeutig von der Bauart her.

--- ZWEI FEHLER AUF DEM WEG, BEIDE LEHRREICH -------------------------

1. DER ERSTE VERSUCH WAR GRUEN UND KAPUTT. Ersetzt wurde mit einem
   Muster: "([^"]*4231[^"]*)". Das hielt

     { host: "127.0.0.1", port: 4231, path: "/404.html" }

   fuer eine Zeichenkette -- ein Muster kann eine oeffnende nicht von
   einer schliessenden Anfuehrung unterscheiden. Heraus kam

     { host: "127.0.0.1`, port: ${PORT}, path: `/404.html" }

   also GUELTIGER Code ohne port-Feld. `node --check` sagte gruen fuer
   alle 164 Dateien. Aufgefallen ist es erst, weil ich vier Vertreter
   gegen eine vorher gemessene Grundlinie laufen liess: pruef-schranke
   39/0 vorher, 38/1 nachher.

   Alles zurueckgenommen und mit einem Zerleger neu gemacht, der weiss,
   ob eine Stelle Code, Zeichenkette, Vorlage, Kommentar oder
   regulaerer Ausdruck ist. In pruef-ics stand die Nummer in einem
   regulaeren Ausdruck -- der wird jetzt gebaut statt hingeschrieben.

2. EIN MODUL, DAS BEIM IMPORTIEREN ARBEITET, IST EINE FALLE. Der
   zweite Durchgang importierte den ersten, um seine Mechanik zu
   benutzen -- und fuehrte dessen Hauptlauf gleich mit aus. Die
   zweiten Nummern wurden dadurch als erste behandelt, zwei Aufrufe
   bekamen dieselbe Nummer, und in pruef-content stand `const PORT`
   zweimal.

--- WAS DAS DAUERHAFT HAELT -----------------------------------------

pruef-portnummern.mjs (neu, 8 Pruefungen) fragt nicht "welche Nummern
sind doppelt", sondern "wer traegt ueberhaupt noch eine von Hand ein"
und "wer startet einen Server, ohne seine Nummer abzuleiten". Die
zweite Frage hat sofort etwas gefunden, das in KEINER Doppelungsliste
stand: pruef-push-weg belegte 4341 und 4342, rief den Waechter aber
gar nicht auf -- dieselbe Nummer wie pruef-agentur. Eine Liste zeigt
nur, was auf ihr steht.

Mit Gegenprobe: Eine unbekannte Datei bekommt keine geratene Nummer,
sondern einen Abbruch, und eine dritte Nummer je Datei gibt es nicht.

--- NACHGEMESSEN ----------------------------------------------------

Sechzehn Pruefungen gegen ihre vorher gemessene Grundlinie, je eine
Vertreterin jeder umgestellten Bauweise (eine Nummer, zwei Nummern,
Nummer in einer Zeichenkette, in einer Vorlage, in einem regulaeren
Ausdruck, dynamische Einfuhr, Nachtlauf mit zwei Laeufen):

  crew-adresse 132/0 · schranke 39/0 · content 45/0 · entwicklung 46/0
  anruf 127/0 · ics 37/0 · push-weg 20/0 · arten 28/0 · video 67/0
  kanalzeile 14/0 · agentur 62/0 · spicy 83/0 · creator-anlegen 50/0
  push-ziel 10/0 · portnummern 8/0

Alle exakt wie vorher. Zwei waren schon vorher rot und sind es
unveraendert geblieben (crew-wand-bild 41/4, teilen 14/3) -- per
`git stash` belegt, nicht angenommen.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-20 22:51:21 +02:00
DogFatherGitandClaude Opus 5 b0e7542507 Der Anrufkasten zeigt, WER dran ist -- nicht dreimal "Verbunden"
Filipe, zu Screen 1: "wenn ich anrufe soll das auch viel besser geiler
und spezieller aussehen aber es soll nicht raushaengen."

Erst gemessen: Es haengt nichts raus. Der Kasten ist 374 px am Handy,
340 am Rechner, 72 Teile geprueft, kein einziges ueber dem Rand. Die
Optik war also gar nicht der Mangel -- zwei andere Dinge waren es.

1. DER NAME DES ANRUFERS GING IMMER VERLOREN.
   `wer(raumName || "Anruf")` lief VOR `kastenBauen()`. Das Element
   #anruf-wer gab es zu dem Zeitpunkt noch nicht, die Zuweisung lief
   ins Leere, und oben stand bei jedem Anruf das Wort "Anruf". Die
   Reihenfolge ist umgedreht: erst bauen, dann beschriften.

2. IN EINEM ANRUF OHNE VIDEO STAND JE PERSON DAS WORT "Verbunden".
   Bei dreien dreimal dasselbe Wort -- und man sah nicht, WER dabei
   war. In einer Gruppe ist genau das die Frage: Ist Kessi schon
   drin? Jetzt steht dort ein Kreis mit den Anfangsbuchstaben und der
   echte Name; waehrend es klingelt ein wartendes Gesicht mit dem
   Namen dessen, den man anruft. Vorher war die Flaeche in dieser
   Sekunde leer, und ein leerer Kasten mit "Es klingelt ..." sieht
   aus wie einer, der haengt -- ausgerechnet im unsichersten Moment.

Der Name kommt NICHT aus einer Abfrage. Wer anruft, bekannt beim
Start nur sich selbst; alle weiteren kommen ausschliesslich ueber das
Ereignis "dabei", und genau dort fehlte das Einsammeln. Deshalb sah
der ANRUFER unter jedem Kreis "Jemand" -- die Angerufenen nicht.

UND DIE PRUEFUNG WAR ZU NACHSICHTIG. Sie verlangte einen Namen
"laenger als ein Zeichen"; der Rueckfalltext "Jemand" ist sechs
Zeichen lang und kam damit durch. Gruen, waehrend der Hauptfall
kaputt war. Die zweite Fassung verglich gegen eine abgeschriebene
Namensliste -- die nannte "Jonas, Luna, Mara, Pat", angelegt werden
aber "Filipe, Rieke, Kessi, Tili", und der Kommentar daneben
behauptete schon da, sie sei abgeleitet. Jetzt ist sie es wirklich:
Die Namen werden beim Anlegen eingesammelt, und geprueft wird, dass
jeder GENAU DIE BEIDEN ANDEREN sieht -- nicht "irgendeinen echten
Namen", denn der eigene Name im eigenen Kasten waere auch einer.

pruef-anruf: 127 Pruefungen, 0 Fehler.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-20 22:25:45 +02:00
DogFatherGitandClaude Opus 5 c59517cb23 Ein Anruf faengt leise an
Filipe: "uebrigens soll man auswaehlen koennen ob man lautsprecher will
oder nicht bitte. es soll ohne lautsprecher anfangen das gespraech und
nur wenn man drauf klickt soll es laut werden ... es soll nicht
raushaengen oder so, das ist nicht cool."

DER GRUND IST DER STREAM. Ein Anruf, der beim Annehmen sofort aus den
Lautsprechern kommt, ist im schlimmsten Fall fuer alle Zuschauer zu
hoeren, bevor irgendjemand reagieren kann. Das laesst sich nicht
zurueckholen.

HIER STAND `hoeren = true` mit der Begruendung: "Wer den Ton abgestellt
hat, will ihn nicht beim naechsten Gespraech wieder an." Das war richtig
gedacht und ist jetzt umgedreht -- aus demselben Grund, nur in die
andere Richtung: Der Zustand soll NICHT ueberdauern. Eine Entscheidung
von vorhin darf nicht fuer ein Gespraech gelten, von dem man noch
nichts wusste. Zurueckgesetzt wird an BEIDEN Wegen, beim Anrufen und
beim Rangehen -- einer allein waere die Haelfte, und die andere Haelfte
faellt niemandem auf, bis es einmal zu laut war.

UND MAN SIEHT ES. Leise anfangen ohne Hinweis waere die naechste
Sackgasse: "ich hoere nichts" ohne Grund und ohne Weg. Ein Balken sagt
es, solange es gilt, und verschwindet in dem Moment, in dem man den Ton
anmacht. Er ist SELBST der Knopf -- wer liest "tippen, um den Ton
anzumachen", will genau das tun. Gedaempftes Bernstein statt Alarmrot:
Es ist kein Fehler, sondern ein Zustand.

DABEI EINEN EIGENEN FEHLER GEFANGEN: Der Balken fragte `!anruf` -- und
diese Variable wird erst gesetzt, NACHDEM der Kasten aufgeht. Beim
Aufbauen blieb er deshalb versteckt, und man sass in einem stummen
Gespraech ohne einen Satz dazu. Genau der Zustand, den er verhindern
soll. Jetzt haengt er am sichtbaren Kasten.

DIE PRUEFUNG WURDE ROT und hat damit ihre Arbeit getan -- sie hielt das
alte Verhalten fest. Umgedreht, nicht gestrichen: Die wichtigste Falle
(ueberlebt der Schalter das Neuzeichnen, wenn jemand auflegt?) bleibt,
nur der erwartete Zustand hat sich gedreht.

GEPRUEFT: pruef-anruf 117/0 (war 114) -- drei Aussagen mehr, darunter
dass der Hinweis da ist, 44 px hoch und im richtigen Moment
verschwindet.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-20 19:01:28 +02:00
DogFatherGitandClaude Opus 5 d6df590224 Telefonieren ging nur ueber UDP -- und die Seite sprang immer hoch
=== 1. WARUM TELEFONIEREN NICHT GING ===

Filipe: "es klingelt erscheint auch alles bei jedem aber telefonieren
klappt immer noch nicht."

GEMESSEN, BEVOR ETWAS GEAENDERT WURDE -- und alles war gruen:

  coturn laeuft                 aktiv
  STUN von aussen               antwortet, nennt meine oeffentliche Adresse
  echte Zuteilung von aussen    7/7, Testpaket kam an (Port 49183)
  Dreier-Anruf im Prueflauf     verbindet, alle hoeren alle

Vier Messungen, vier Mal in Ordnung -- und das Telefon ging trotzdem
nicht. Der Grund: Der Prueflauf faehrt BEIDE Seiten auf demselben
Rechner. Dort finden sie sich ueber die direkte Adresse, und die
Vermittlung wird nie gebraucht. Draussen sitzen sie in verschiedenen
Netzen.

DER FUND: Eingetragen war eine einzige Adresse --

    turn:159.195.212.167:3478

Ein `turn:` OHNE `?transport=` heisst UDP, und nur UDP. Nachgemessen
hoert coturn aber auf beidem: 3478/UDP offen, 3478/TCP offen (5349/TLS
ist zu). Angeboten wurde nur der halbe Server.

Wer in einem Netz sitzt, das UDP nach draussen sperrt -- Mobilfunk mit
strengem Profil, Gast-WLAN, Firmennetz --, bekam damit KEINEN
Vermittlungsweg. Es klingelt (das laeuft ueber die Website, also ueber
443), und danach passiert nichts. Genau das gemeldete Bild.

Aus einem Eintrag werden jetzt drei Wege:
  stun:host:port                die eigene Adresse finden, ohne Vermittlung
  turn:host:port?transport=udp  der schnelle Weg
  turn:host:port?transport=tcp  der Weg durch fast jede Sperre

ABGELEITET, NICHT EINGETRAGEN: Drei Zeilen von Hand waeren drei
Stellen, an die beim naechsten Serverumzug jemand denken muesste --
die abgeschriebene Liste, die hier schon zweimal teuer war. Ein
Eintrag MIT `?transport=` bleibt unangetastet, ein fremder Dienst mit
eigenem Passwort sowieso.

pruef-turn-wege (neu, 15/0) sichert beides: dass jeder Weg herauskommt
UND dass jeder turn:-Weg Zugangsdaten traegt. Das Zweite ist das
wichtigere -- auffaechern ohne anmelden haette den Fehler nur
verschoben.

=== 2. DIE SEITE SPRANG BEIM ZURUECKGEHEN IMMER HOCH ===

Filipe: "das nervt man muss dan immer wieder runter scrollen bis man
da ist wo man vorher war."

Der Browser versucht es sogar -- scrollRestoration steht ab Werk auf
"auto". Nur: Jede Seite hier kommt fast leer an und holt ihren Inhalt
danach per Abruf. In dem Moment, in dem der Browser die alte Position
wiederherstellen will, ist das Dokument ein paar hundert Pixel hoch.
Er kann nicht auf Zeile 900 springen, die es noch nicht gibt -- und er
versucht es kein zweites Mal.

Jetzt macht es kopf.js selbst (gilt damit auf allen 21 Seiten): Stelle
merken beim Verlassen, beim Oeffnen zurueckholen und jeden Bildaufbau
lang versuchen, bis das Dokument hoch genug ist. Nach drei Sekunden
wird aufgegeben.

Drei Dinge sind Absicht:
  - NUR beim Zurueckgehen, nicht bei jedem Oeffnen. Wer eine Kachel
    anklickt, will oben anfangen.
  - WER SELBST SCROLLT, GEWINNT. Rad, Wisch oder Taste beenden das
    Nachspringen sofort.
  - Nach einer Stunde vergessen -- auf Zeile 900 zu landen, weil man
    gestern dort war, ist keine Hilfe.

=== 3. DIE LISTE IST NACH ROLLE GETRENNT ===

Filipe: "die rechte hand rolle immer zuerst und dan die modis. die
sollen auch schoen getrennt sein und verschieden aussehen also die
rechte hand viel spezieller."

Die Reihenfolge macht der Server ueber ROLLEN_SORTIERUNG -- dieselbe
Konstante wie ueberall sonst im Haus, nicht eine zweite. Eine
Sortierung nach ROLLE ist ausdruecklich keine Rangliste: Sie sagt
nichts darueber, wie gut jemand ist, nur welche Aufgabe er hat.
Deshalb steht ueber jeder Gruppe ein Satz und keine Zahl.

"Spezieller" heisst hier Material, nicht Groesse: eigener Farbton als
Kante und Schimmer, hellere Flaeche, kraeftigerer Name. Beide Karten
sind GLEICH GROSS und tragen dieselben Zeilen -- zwei Sorten Aufgabe,
keine Rangfolge. Im Kontrastmodus traegt eine doppelte Kante die
Unterscheidung, weil dort keine Farbe mehr wirkt.

=== 4. DER ANRUFKASTEN ===

Vorher: vier gleich aussehende Pillen nebeneinander, darunter eine
Geraeteauswahl, die das breiteste Element war. Kein Name, keine
Ordnung, und der einzige Knopf mit Folgen sah aus wie die anderen.

  - MAN SAH NICHT, MIT WEM. Der wichtigste Satz eines Telefonats fehlte.
    Der Chat kennt den Namen und gibt ihn jetzt mit; wer rangeht, sieht
    den Anrufer.
  - "MIKRO AN" WAR ZWEIDEUTIG -- "ist an" oder "schalt an"? Wer falsch
    raet, sitzt stumm da. Jetzt traegt ein durchgestrichenes Zeichen
    den Zustand, das Wort nur die Sache. aria-pressed bleibt die
    Wahrheit, auch fuer Vorleseprogramme.
  - DIE GERAETEAUSWAHL liegt hinter einem kleinen Knopf, der nur
    erscheint, wenn es ueberhaupt etwas zu waehlen gibt.
  - AUFLEGEN ist als einziger Knopf farbig und steht immer rechts.
  - Ein ruhiger Puls atmet, solange die Verbindung aufbaut, und haelt
    an, sobald sie steht. Bei prefers-reduced-motion steht er still.

=== NEBENBEFUND, DEN DIE PRUEFUNG GEFUNDEN HAT ===

pruef-anruf wurde durch die Auffaecherung an vier Stellen rot: Sie
griff `adressen[0]` und erwartete dort Zugangsdaten. Das ist jetzt der
STUN-Weg, und der hat richtigerweise keine. Gesucht wird nicht mehr
nach PLATZ, sondern nach ART -- ein Index ist eine Annahme darueber,
wie die Liste aussieht, und die hat sich gerade geaendert.

Dazu angepasst: Die Pruefung "die Uhr laeuft" verlangte, dass die Uhr
schon beim Klingeln zaehlt. Seit heute frueh beginnt sie beim
Rangehen (sonst standen im Kasten und im Chat zwei verschiedene
Zahlen). Sie prueft jetzt das Gegenteil -- statt sie zu streichen,
denn eine Zeile weniger haette den Fehler beim naechsten Umbau wieder
durchgelassen.

GEMESSEN: pruef-anruf 114/0 (vorher 111 -- drei MEHR, weil die
Auffaecherung mitgeprueft wird), pruef-turn-wege 15/0,
pruef-meldungen 8/0, pruef-css-klassen ALLES IN ORDNUNG,
pruef-rechtetafel 19/0.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-19 15:23:15 +02:00
DogFatherGitandClaude Opus 5 d766be57b6 Anruf: wer auflegt, hinterlaesst jetzt auch eine Spur
Filipe, 00:30 Uhr: „hab angerufen aufgelegt und da steht immer noch
nichts im chat vom anruf...."

Im Protokoll: vier Anrufe, je drei bis zehn Sekunden, kein einziger
Eintrag im Gespraech. Und mein Denkfehler dahinter war einfach:

„Verpasster Anruf" stand NUR in `aufraeumen()` -- also erst, wenn ein
Anruf nach zwei Minuten von selbst verfaellt. Wer vorher auflegt,
loeschte ihn spurlos. Abschnitt 5e hat damit genau den Weg geprueft,
den niemand geht: warten, bis es von allein aufhoert. Im Alltag legt
man auf.

Fuer die andere Seite ist ein Anrufer, der aufgibt, ERST RECHT ein
verpasster Anruf.

UND EINE ZWEITE FALLE, die die erste verdeckt hat: `verpasstVermerken`
nahm den Anrufer aus der Runde der Anwesenden (`a.wer`). Beim
Verfallen steht er noch darin -- beim AUFLEGEN ist sie leer. Die
Zeile waere also auch dann ausgefallen, wenn der Aufruf an der
richtigen Stelle gestanden haette. Jetzt kommt er aus `a.anrufer`,
wie beim gefuehrten Anruf auch.

Zwei Fehler, die einander gedeckt haben, und beide hat dieselbe
Pruefluecke durchgelassen: Ich hatte den Ablauf geprueft, den ich
gebaut hatte, nicht den, den ein Mensch geht.

Geprueft: pruef-anruf 106 -> 111. Der neue Abschnitt ruft an, wartet
kurz, legt auf -- und misst, dass genau EINE Zeile entsteht, dass sie
„verpasst" sagt, dass der Anrufer daran steht und dass spaeteres
Aufraeumen keine zweite schreibt.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-19 00:32:57 +02:00
DogFatherGitandClaude Opus 5 00459f47c6 Anruf: "Anruf · 3:42" steht jetzt im Chat, nicht nur der verpasste
Filipe: „im chat soll auch stehen verpasster anruf oder anruf gehabt
und wie lange."

Die zweite Haelfte. Ein verpasster Anruf steht seit heute drin -- ein
GEFUEHRTER stand nirgends. Nach dem Auflegen war das Gespraech spurlos
weg.

Tage spaeter ist die Frage aber nicht „hat er angerufen", sondern
„haben wir das besprochen oder nur kurz telefoniert?". Drei Minuten
sind ein Gespraech, zwoelf Sekunden sind ein Verwaehlen. Eine Zeile
ohne Zahl beantwortet das nicht.

GEZAEHLT AB DEM RANGEHEN, nicht ab dem Klingeln. Sonst staende bei
jedem Anruf eine halbe Minute zu viel darin -- die Zeit, in der das
Telefon nur laeutete. Dafuer merkt sich der laufende Anruf jetzt zwei
Dinge zusaetzlich:

  * `gespraechSeit` -- gesetzt, wenn der Zweite dazukommt.
  * `anrufer`       -- wer begonnen hat. `wer` ist die Runde der
                       gerade Anwesenden; sie leert sich, und am Ende
                       laesst sich daraus nicht mehr ablesen, von wem
                       der Anruf ausging. Die Zeile steht bei ihm, wie
                       bei jedem Telefon.

Unter einer Minute steht die Sekundenzahl („7 Sekunden"), darueber
Minuten („3:42 Minuten") -- „0:07" liest sich umstaendlicher, als es
ist.

Geprueft: pruef-anruf 101 -> 106. Der neue Abschnitt fuehrt einen
echten Anruf: anrufen, rangehen, kurz warten, beide auflegen -- und
misst dann, dass genau EINE Zeile entsteht, dass sie „Anruf" sagt und
nicht „verpasst", und dass die Dauer in SEKUNDEN steht. Stuende dort
die Zeit seit dem Klingeln, waeren es Minuten.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-19 00:02:58 +02:00
DogFatherGitandClaude Opus 5 c87c396ef5 Anruf: es klingelt jetzt wirklich -- vorher gab es gar keinen Ton
Filipe: „ist das normal dass es nicht mal klingelt wenn man anruft?"

Nein. Und der Grund war so einfach wie peinlich: Es gab ueberhaupt
keinen Ton. Weder beim Anrufer noch beim Angerufenen. Der Anruf zeigte
einen Kasten auf dem Bildschirm, sonst nichts -- wer nicht zufaellig
hinsah, merkte nichts, und wer anrief, wusste nie, ob ueberhaupt etwas
passiert.

Damit erklaert sich auch, warum heute Abend alles „kaputt" wirkte,
obwohl Verbindung, Raum, Anmeldung und Code stimmten: Ein stummes
Telefon ist von einem defekten nicht zu unterscheiden.

ZWEI TOENE, JE NACH SEITE:
  Angerufener  zwei weiche Toene (880/660 Hz), dann Pause -- alle
               2,4 Sekunden, dazu Vibration, wo das Geraet sie kann.
  Anrufer      ein leiseres, langsameres Freizeichen (440 Hz). Ein
               stiller Kasten mit laufender Uhr sagt nur, DASS etwas
               passiert; ein Freizeichen sagt, was.

OHNE TONDATEI. Ein .mp3 waere eine Datei mehr im Netz, eine
Lizenzfrage und ein Ladefehler, der genau dann auffaellt, wenn man ihn
braucht. Zwei Sinustoene aus dem Browser reichen. Sanft ein- und
ausgeblendet, weil ein hart geschalteter Sinus knackt -- und das
Knacken ist das Unangenehme daran.

Der Ton hoert auf, sobald jemand rangeht (`dabei`), sobald die
Verbindung steht und beim Auflegen. Drei Stellen, weil ein
Freizeichen, das waehrend des Gespraechs weiterlaeuft, das ist, was
man an Telefonanlagen hasst.

EHRLICH DAZU: Browser lassen Ton erst zu, wenn jemand auf der Seite
etwas angeklickt hat. Wer die Seite gerade erst geoeffnet hat, hoert
unter Umstaenden nichts -- dann bleibt die Benachrichtigung des
Geraets der Weg. Das ist eine Regel des Browsers, keine Entscheidung
von uns.

Geprueft: pruef-anruf 100 -> 101. Gemessen wird am Oszillator, nicht
am Lautsprecher: Ob wirklich etwas zu hoeren ist, haengt an Geraet und
Lautstaerke -- dass die Seite Toene ERZEUGT, laesst sich messen.
Gegenprobe (Freizeichen ausgebaut): „0 Toene erzeugt", rot.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-18 23:59:25 +02:00
DogFatherGitandClaude Opus 5 31906e9216 Pruefung berichtigt: die kurze Klingeldauer galt fuer den ganzen Lauf
Der Commit davor ging mit EINEM roten Test hinaus -- ich habe das
Ergebnis nicht angesehen, weil Pruefung und Commit in derselben Kette
liefen. Das war der Fehler, nicht der rote Test selbst.

Die Ursache war die Pruefung, nicht der Code: ANRUF_KLINGELT_SEKUNDEN
stand auf 2, und das gilt fuer den GANZEN Lauf. Im Browser-Teil
vergehen zwischen "anrufen" und "der Server kennt den Anruf" mehrere
Sekunden -- dort war er damit schon verfallen. Jetzt zehn Sekunden:
lang genug fuer den Browser, kurz genug, dass Abschnitt 5e nicht zur
Geduldsprobe wird.

100 Pruefungen, 0 Fehler.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-18 23:32:44 +02:00
DogFatherGitandClaude Opus 5 e43fe97550 Anruf: zwei Minuten Zeit -- und ein verpasster Anruf bleibt sichtbar
Filipe: „das mit dem anruf klappt nicht … es geeeeeht einfach nicht."

NACHGEMESSEN WAR SERVERSEITIG ALLES IN ORDNUNG: Der Anruf kam an
(Protokoll), der Raum stimmte (Dogfather + VanVan), sie war angemeldet
(Sitzung bis morgen frueh), sie hat ein Geraet fuer
Benachrichtigungen, und der neue Code lief. Trotzdem ging es nicht --
und zwar aus zwei Gruenden, die beide nichts mit Technik zu tun haben,
sondern mit Zeit und Sichtbarkeit.

1. 45 SEKUNDEN SIND KEIN KLINGELN. Bei einem Telefon hebt man ab.
   Hier muss in dieselben Sekunden: Benachrichtigung zustellen,
   bemerken, Handy entsperren, antippen, App laden, Anmeldung pruefen.
   Das reicht, wenn man das Geraet in der Hand haelt -- und sonst nie.
   Jetzt zwei Minuten: lang genug, um aus der Tasche zu kommen, kurz
   genug, dass niemand vor einem Anruf sitzt, den es nicht mehr gibt.

2. UND DANACH WAR ES, ALS WAERE NIE ETWAS GEWESEN. Ein verpasster
   Anruf verschwand lautlos: keine Zeile, kein Hinweis, nicht einmal,
   DASS jemand angerufen hat. Genau das fuehlt sich an wie „geht
   nicht", auch wenn alles funktioniert hat. Jedes Telefon der Welt
   zeigt einen verpassten Anruf; jetzt steht er als Zeile im Gespraech,
   beim Anrufer, mit Uhrzeit.

Geprueft: pruef-anruf 95 -> 100. Der neue Abschnitt misst den echten
Ablauf (anrufen, niemand geht ran, Zeile erscheint) und dass sie NUR
EINMAL erscheint -- `aufraeumen()` laeuft bei jeder Anfrage mit.

Dafuer ist die Klingeldauer ueber die Umgebung einstellbar: Eine
Pruefung, die zwei Minuten wartet, wird abgeschaltet, und dann waere
der verpasste Anruf wieder ungeprueft. Im Betrieb steht die Variable
nirgends.

Und noch ein eigener Messfehler: Die Pruefung las zuerst
/api/chat/raum/:id -- die Route heisst /api/chat/raeume/:id/nachrichten.
Sie meldete „0 Nachrichten, vorher wie nachher" und sah damit aus wie
ein Befund ueber den Code.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-18 23:30:52 +02:00
DogFatherGitandClaude Opus 5 4ea7b37333 Anruf: "sie kriegt nur eine Benachrichtigung aber keinen Anruf"
Filipe, gerade gemeldet. Es waren ZWEI Fehler, und beide erklaeren
genau das, was er gesehen hat.

--- 1. Die Benachrichtigung sagte nicht, dass es ein Anruf ist ------

Gemessen kam bei ihr an:

    Nachricht von [object Object]
    (kein Text)

`nachricht.von` ist beim Klingeln ein OBJEKT (`{id, name}`) und keine
Zeichenkette; einen `text` gibt es bei einem Anruf gar nicht. Moeglich
wurde beides, weil die ART des Ereignisses die Benachrichtigung nie
erreichte: `chatEreignis` nimmt sie als vierten Parameter entgegen,
reichte sie aber nur in den Ereignisstrom weiter. Fuer den Push galt
jedes Ereignis als Chatnachricht -- auch das Klingeln.

Jetzt steht dort "Filipe ruft an" / "Tippen zum Rangehen", und beim
Tippen landet man im richtigen Gespraech.

--- 2. Und dort klingelte es dann trotzdem nicht --------------------

Das Klingeln lief ausschliesslich ueber den offenen Ereignisstrom. Wer
zusieht, hoert es. Wer die Seite NICHT offen hat, bekommt die
Benachrichtigung, tippt darauf, die Seite laedt -- und bleibt still.
Das Ereignis war vorbei, bevor sie da war.

Der Anruf funktionierte damit ausgerechnet fuer die nicht, fuer die
die Benachrichtigung ueberhaupt gebaut wurde.

Neu: `GET /workspace/api/anruf/offen` -- beim Laden fragt die Seite
einmal nach, ob in einem ihrer Raeume jemand wartet. Nur was noch
klingelt (45 s), nur Raeume, in denen die Person drin ist, und nicht
beim Anrufer selbst.

--- Gepruefte Wege -------------------------------------------------

pruef-anruf 80 -> 95. Zwei neue Abschnitte, beide mit Gegenprobe:
Route stillgelegt -> 3 rot; der Text ist jetzt einzeln pruefbar
(`pushTextFuer`), weil er vorher tief in einer Funktion entstand, die
nur der Push-Weg aufruft -- von aussen nicht messbar.

Nebenbei gefunden: `istDrinFuerAnruf` braucht die PERSON, nicht ihre
Nummer, und sagt das mit einem eigenen TypeError. Meine erste Fassung
uebergab die Nummer.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-18 23:01:13 +02:00
DogFatherGitandClaude Opus 5 e933de3248 Anruf: der Lautsprecher -- und warum es kein Freisprech-Knopf ist
Filipe: "die funktion lautsprecher fehlt."

Er ist das Gegenstueck zum Mikrofon: "Mikro aus" heisst, die anderen
hoeren MICH nicht -- "Lautsprecher aus" heisst, ICH hoere sie nicht.
Beides braucht man, aus verschiedenen Gruenden: das Mikro, wenn es bei
einem selbst laut ist; den Lautsprecher, wenn nebenher etwas laeuft
oder jemand ins Zimmer kommt.

WAS BEWUSST NICHT GEBAUT WURDE: der Umschalter zwischen Hoermuschel
und Freisprechen, den ein Telefon hat. Recherchiert statt geraten --
den gibt es im Browser nicht: Auf dem iPhone entscheidet Safari
selbst, wohin der Ton geht, und Android laesst einzelne Toene gar
nicht auf ein anderes Geraet legen (setSinkId ist dort nicht
verfuegbar, weil die Plattform es nicht hergibt). Ein Knopf, der dort
nichts tut, waere schlimmer als keiner -- man drueckt ihn und sucht
den Fehler dann bei sich.

Wo es GEHT (Rechner mit Chrome, Edge, Safari), steht dafuer eine echte
Geraeteauswahl daneben. Sie erscheint nur, wenn es mehr als ein Geraet
gibt UND die Namen bekannt sind; eine Auswahl mit einem Eintrag ist
keine Auswahl, sondern eine Behauptung.

DIE PRUEFUNG, AUF DIE ES ANKOMMT: Kommt jemand dazu oder geht jemand,
werden die Toenelemente NEU GEBAUT -- und neue Elemente wissen nichts
von einem Schalter, der vorher umgelegt wurde. Genau daran scheitern
solche Knoepfe sonst: Sie wirken, bis sich die Runde aendert, und dann
hoert man ploetzlich wieder mit. Gegenprobe (Anwenden nach dem
Neuzeichnen ausgebaut) macht genau diese eine Pruefung rot, waehrend
alle anderen gruen bleiben.

Geprueft: pruef-anruf 73 -> 80, im echten Dreiergespraech im Browser.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-18 17:08:31 +02:00
DogFatherGitandClaude Opus 5 775c4b4207 Vermittlungsserver: coturn mit Zugangsdaten, die verfallen
Damit Anrufe auch in Netzen zustande kommen, die keine direkte
Verbindung zulassen (15-25 % der Faelle). coturn ist installiert,
steht aber still, bis die Konfiguration liegt -- ein coturn mit
Werkseinstellung ist ein offenes Relais.

KEIN FESTES PASSWORT. Es laege dauerhaft im Browser jedes
Team-Mitglieds und liesse sich nie entziehen. Der Server rechnet
stattdessen bei jeder Abfrage Zugangsdaten, die nach zwoelf Stunden
verfallen (server/workspace-turn.js, coturns `use-auth-secret`). Das
Geheimnis liegt in einer Datei, nicht in der Datenbank: einstellung-
Setzen() schreibt Werte ins Protokoll, und coturn braucht denselben
Wert ohnehin in /etc.

Die Oberflaeche frischt die Daten vor jedem Anruf auf. Der Chat ist
eine App, die tagelang offen bleibt -- wer nur beim Laden holt,
telefoniert am zweiten Tag ohne Vermittlung, und es faellt nicht auf:
Es scheitern nur die, die sie gebraucht haetten.

DIE WICHTIGSTE ZEILE DER KONFIGURATION ist die Sperrliste. Gemessen:
dreizehn Dienste lauschen auf diesem Server nur oertlich, darunter
Caddys Verwaltung auf 127.0.0.1:2019 -- wer sie erreicht, kann jede
Website umleiten. Ohne Sperrliste waere der Vermittlungsserver die
Tuer dorthin, und die Anfrage saehe fuer Caddy aus wie von localhost.

Geprueft: pruef-anruf.mjs 51 -> 73 Pruefungen. Gegenprobe (Geheimnis
als Passwort ausliefern + fremde Zugangsdaten ueberschreiben) macht
genau 5 rot, darunter "das Geheimnis steht NIRGENDS in der Antwort".

tools/turn-probelauf.sh beweist am echten coturn, was ein fester
Vergleichswert nicht kann: dass coturn unsere Rechnung akzeptiert.
Beide Seiten koennten sonst konsequent falsch rechnen und jede
Pruefung waere gruen. Seine eigene Gegenprobe war zweimal zu Recht
rot -- der erste Aufbau mass wegen `-y` gar nicht das Ziel, das er zu
messen behauptete, sondern coturns eingebauten Loopback-Schutz.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-18 15:22:14 +02:00
DogFatherGitandClaude Opus 5 5bbf1f2019 Der Anruf funktioniert wirklich -- gefunden im Dreier-Test
GEBAUT WAR NICHT BEWIESEN. Der Anruf ist fuer bis zu vier Leute
geschrieben, geprueft war er zu zweit -- und der Zweier-Test pruefte
nur, dass der Kasten erscheint und die Uhr laeuft. Ob jemals TON
ankommt, hat er nie gefragt.

Der Dreier-Test hat es gefragt. Ergebnis beim ersten Lauf:

  DogFather      0 Stroeme, "Verbunden"
  rechte Hand    0 Stroeme, "Verbunden"
  der Modi       0 Stroeme, "Verbindung ..."

DREI FEHLER, gefunden durch Messen statt Raten:

1. SIGNALE, DIE ZU FRUEH KOMMEN, WURDEN WEGGEWORFEN -- und das haette
   JEDEN Anruf getroffen, auch zu zweit.

   Wer rangeht, meldet "dabei". Der Anrufer bekommt das sofort und
   schickt sein Angebot. Der Angerufene braucht danach aber noch ein
   bis zwei Sekunden fuer das Mikrofon, und in dieser Zeit war `anruf`
   noch null. Das Angebot wurde still verworfen; danach kommt keins
   mehr. Beide Seiten zeigten "Verbindung ...", und es passierte nie
   wieder etwas.

   In der Spur war es unuebersehbar, sobald man hinsah:
     ereignis:signal inhalt=angebot     <- kommt an
     ereignis:signal inhalt=weg
     verbindung angelegt                <- erst JETZT

   Ein Signal ist keine Nachricht, die man wiederholen kann. Wer es
   wegwirft, hat den Anruf verloren -- es wird jetzt aufgehoben und
   abgearbeitet, sobald das Mikrofon steht.

2. DIE WARTESCHLANGE LAG ZUERST AN DER FALSCHEN STELLE. Eingebaut in
   `signalVerarbeiten`, verworfen wurde aber schon eine Ebene hoeher.
   Gefunden erst, als die Diagnose zeigte, dass `signalVerarbeiten` NIE
   aufgerufen wird: keine einzige Konsolenzeile, obwohl beide Zweige
   dort etwas melden. Eine Reparatur an der falschen Stelle sieht aus
   wie eine Reparatur.

3. "VERBUNDEN" STAND DA, BEVOR ETWAS VERBUNDEN WAR. Die Meldung wurde
   gesetzt, wenn jemand RANGEHT -- die Verbindung braucht danach noch
   Sekunden. Den richtigen Zeitpunkt kennt nur die Verbindung selbst
   (`onconnectionstatechange`). Eine Anzeige, die etwas Falsches sagt,
   ist schlimmer als keine.

Dazu zwei Kleinigkeiten, die dabei auffielen: Das Abarbeiten laeuft
jetzt NACHEINANDER (ein Verbindungsweg, der vor der Beschreibung
ankommt, wird abgewiesen -- parallel gewinnt oft der Weg), und die
Warteschlange wird beim Auflegen geleert, damit kein altes Signal in
den naechsten Anruf wandert.

Und die Pruefung selbst hatte einen Fehler: Abschnitt 6 setzt erfundene
STUN-Adressen und liess sie stehen. Der Browser haette im naechsten
Abschnitt gegen deren Zeitlimit gekaempft statt gegen den Code. Sie
werden jetzt wieder geleert -- eine Pruefung, die der naechsten den
Boden verstellt, macht deren Ergebnis unlesbar.

pruef-anruf 40 -> 51. Der neue Abschnitt prueft das, worauf es
ankommt: Bei drei Teilnehmern muss JEDER zwei Stroeme haben. Haette
`verbindungen` eine einzelne Variable statt einer Karte, ginge es zu
zweit gut und der Dritte ueberschriebe stillschweigend den Ersten --
man sieht sich zu dritt und hoert einen.

Gegenprobe (Warteschlange wieder ausgebaut): 4 rot, genau die
Tonpruefungen.

Gruen: anruf, chat, css-klassen, namen.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-18 14:42:44 +02:00
DogFatherGitandClaude Opus 5 93c9788782 Telefonieren im Chat -- und die Farben nach Nachbarschaft verteilt
ZWEI SACHEN IN EINEM COMMIT, weil beide aus derselben Nacht stammen.

=== 1. DIE FARBEN: DER RICHTIGE ABSTAND ===

Filipe: "es gibt noch mehr die aehnlich aussehen von den farben also
leg los alle die sich aehnlich sind von den farben wechseln."

Er hatte recht, und mein Denkfehler laesst sich benennen: Ich hatte den
kleinsten Abstand ueber ALLE 37 Paare maximiert -- und der liegt bei 37
Farben zwangslaeufig bei 0,10. Das ist die Packungsgrenze, kein
Versaeumnis.

NUR SIEHT NIEMAND ALLE 37 NEBENEINANDER. Man sieht NACHBARN. Im Browser
gemessen, an der tatsaechlichen Lage auf dem Schirm -- 125 Paare, die
wirklich nebeneinander stehen, 15 davon unter 0,15:

  Creator-Profile / Zahlen      0,1019   beide rosa-rot
  LIVE-Analyse / Technik        0,1030   beide orange
  Wunschliste / Meldungen       0,1032   beide gelbgruen
  Wer sieht was / Entwicklung   0,1039   beide cyan
  Regeln & Hilfe / Mitmachen    0,1047   beide gruen
  Der Treff / Anschlagbrett     0,1070   beide rosa

Die Farben bleiben, ihre ZUTEILUNG aendert sich:

  Nachbarabstand     0,1047 -> 0,2133   (mehr als verdoppelt)
  Nachbarn unter 0,15     6 -> 0

Die Nachbarschaft steht in server/kachel-nachbarn.json, gemessen im
Browser -- nicht aus der Struktur im Quelltext abgeleitet. Die sagt,
was zusammengehoert, nicht was zusammen zu sehen ist.

=== 2. TELEFONIEREN IM CHAT ===

Filipe: "kann man machen dass die modis, rechte hand und ich auch
telefonieren koennen im chat?" ... "was man selbst in die app
reinsetzten kann, nicht meinen pc belastet und trotzdem vielleicht in
gruppe, mit video oder einzelnd."

DER TON GEHT NICHT UEBER DEN SERVER. Direkt von Browser zu Browser
(WebRTC); der Server reicht nur die Verbindungsdaten weiter, ein paar
Kilobyte je Anruf. Zu zweit kodiert jedes Geraet einen Strom und
dekodiert einen -- die Last eines gewoehnlichen Videoanrufs.

KEIN NEUER DIENST. Der Chat hatte bereits alles: `chatEreignis()` fuer
den Hinweg (SSE), Push fuers Klingeln, Raeume mit mehreren
Teilnehmern. Ein eigener WebSocket daneben waere eine zweite
Verbindung fuer dieselbe Frage -- und die zweite wird beim naechsten
Umbau vergessen.

Gebaut: Ton und Video, einzeln und in Gruppe bis GRUPPE_MAX (4),
Klingeln mit Annehmen/Ablehnen, Mikro und Kamera schaltbar,
Gespraechsdauer, Auflegen. Der Chat bleibt daneben benutzbar -- man
schreibt oft, waehrend man spricht.

EIN FUND, DER OHNE PRUEFLAUF LIVE GEGANGEN WAERE: Der Server sperrt
Mikrofon und Kamera per Permissions-Policy auf ALLEN Seiten. Der erste
Lauf meldete "microphone is not allowed in this document" -- der Anruf
haette bei JEDEM versagt, mit einer Meldung, die auf die falsche
Faehrte fuehrt (man sucht an den Browsereinstellungen). Die Sperre
bleibt ueberall und ist an genau EINER Stelle geoeffnet: der
Chat-Seite, und nur fuer sie selbst (`self`, nicht `*`).

NOCH NICHT GEBAUT -- und ausdruecklich nicht heimlich: coturn. Ohne
Vermittlungsserver klappen Anrufe nur im selben Netz. Die Adressen
stehen in den Einstellungen statt im Quelltext; sie lassen sich
nachtragen, ohne eine Zeile zu aendern. Ein oeffentlicher STUN-Dienst
als Standard kam nicht in Frage: Er saehe bei jedem Anruf die
IP-Adressen beider Teilnehmer, und fuer ein Team, das ueber Moderation
und Vorfaelle spricht, ist das keine Kleinigkeit.

server/pruef-anruf.mjs, 40 Pruefungen. Der wichtigste Abschnitt: Wer
nicht in den Raum gehoert, kommt an KEINE Route. Ein Anruf hinterlaesst
keine Spur -- wer mithoert, faellt nicht auf.

Gegenproben, jede zielgenau:
  Empfaengerpruefung der Signalisierung weg -> 1 rot
  Raumpruefung weg                         -> 5 rot (jede Route offen)
  Kopfzeilen-Ausnahme weg                  -> 4 rot

Gruen: anruf, chat, chat-optik, chat-kanaele, css-klassen, namen,
struktur.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-18 10:10:04 +02:00