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]>
This commit is contained in:
2026-09-18 10:10:04 +02:00
co-authored by Claude Opus 5
parent 438493c7dc
commit 93c9788782
42 changed files with 2492 additions and 409 deletions
+25 -1
View File
@@ -53,6 +53,7 @@ import { checklisteRouter } from "./workspace-checkliste.js";
import { teamlageRouter } from "./workspace-teamlage.js";
import { pushRouter, pushStarten } from "./workspace-push.js";
import { chatRouter } from "./workspace-chat.js";
import { anrufRouter } from "./workspace-anruf.js";
import { videoRouter } from "./workspace-video.js";
import { bewerbungRouter } from "./workspace-bewerbung.js";
import { leistungRouter } from "./workspace-leistung.js";
@@ -75,7 +76,27 @@ app.use((req, res, next) => {
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("X-Frame-Options", "SAMEORIGIN");
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
res.setHeader("Permissions-Policy", "geolocation=(), microphone=(), camera=(), payment=()");
/* MIKROFON UND KAMERA SIND GESPERRT -- AUSSER IM CHAT (18.09.2026).
Diese Sperre ist kein Versehen, sie ist der Schutz: Ohne sie
koennte jedes eingeschleuste Skript auf JEDER Seite das Mikrofon
anschalten. Sie bleibt deshalb ueberall stehen und wird an genau
EINER Stelle geoeffnet -- dort, wo telefoniert wird.
GEFUNDEN HAT DAS DIE PRUEFUNG, nicht das Lesen: Der Anrufkasten
meldete "Der Browser laesst das Mikrofon nicht zu", und in der
Konsole stand "microphone is not allowed in this document". Ohne
diesen Lauf waere der Anruf live gegangen und haette bei jedem
versagt -- mit einer Meldung, die auf die falsche Faehrte fuehrt
(man sucht dann an den Browsereinstellungen).
`(self)` und nicht `*`: Nur diese Seite selbst, keine
eingebetteten Fremdinhalte. */
const chatSeite = /^\/workspace\/chat\.html/.test(req.path);
res.setHeader("Permissions-Policy",
chatSeite
? "geolocation=(), microphone=(self), camera=(self), payment=()"
: "geolocation=(), microphone=(), camera=(), payment=()");
/* HSTS NUR ÜBER EINE GESICHERTE VERBINDUNG (Befund 04.09.2026, Audit).
@@ -213,6 +234,9 @@ app.use(pushRouter);
alle Chat-Wege still 404 und niemand sieht warum. Der zweite Fall ist
der unangenehmere: Die Seite laedt, der Chat bleibt leer. */
app.use(chatRouter);
/* Der Anruf nutzt den Ereigniskanal des Chats -- deshalb direkt
dahinter, damit die Reihenfolge die Abhaengigkeit zeigt. */
app.use(anrufRouter);
/* DER VIDEO-WEG (17.09.2026), Stufe A des Video-Plans. Er liegt VOR
dem Bereichs-Router: Beide horchen auf `/workspace/api/bereich/...`,
und `/:bereich/:id` wuerde "video" sonst fuer eine Nummer halten --