Chat: "@rudel" ruft das ganze Team -- und nur die vier duerfen es
Filipe: "dan will ich auch dass nur die modis, rechte hand, linke hand
und dogfather, alle auch auf einmal markieren koennen im chat mit
einem, @rudel ,dann sollen alle eine benarichtigung bekommen."
DAS WAR EINE OFFENE FRAGE, UND ER HAT SIE BEANTWORTET
-----------------------------------------------------
In chat-erwaehnung.js stand seit dem 20.09. woertlich: "KEIN @alle. Es
waere in fuenf Minuten gebaut und ist der zuverlaessigste Weg, dass alle
die Benachrichtigungen abschalten -- und dann kommt auch die an, die
wirklich fuer einen bestimmten Menschen gedacht war. Wenn Filipe es
ausdruecklich will, gehoert dazu eine Entscheidung, WER es benutzen
darf; das ist eine Frage an ihn, keine, die ich hier beantworte."
Seine Antwort ist genau diese Entscheidung, und sie ist die Sicherung:
Team Dogi und DogFather duerfen rufen, sonst niemand.
WEN DER RUF ERREICHT: DAS TEAM IM RAUM, NICHT DEN RAUM
------------------------------------------------------
"Rudel" heisst das Team -- dieselben vier Gruppen, die auch rufen
duerfen. In einem Team-Kanal ist das jeder Anwesende, also "alle auf
einmal". Sichtbar wird der Unterschied im TREFF, und dort haette die
andere Lesart wehgetan: Dort sitzt die Community. Ein Ruf, der jeden
Zuschauer weckt, waere etwas anderes als der bestellte -- und beim
zweiten Mal haetten sie die Benachrichtigungen abgeschaltet.
WO WAS ENTSCHIEDEN WIRD
-----------------------
chat-erwaehnung.js bleibt ohne Abhaengigkeiten (die Pruefung soll sie
lesen koennen, ohne einen Server hochzufahren). Sie sagt nur, DASS
gerufen wurde; der Aufrufer sagt ihr, ob der Schreibende darf. Wer zum
Rudel gehoert, entscheidet workspace-chat.js ueber istTeamDogi().
Das Schluesselwort gewinnt gegen einen Menschen, der "Rudel" heisst.
Heute heisst niemand so -- aber das ist eine Tatsache von heute, kein
Gesetz. So herum verliert niemand eine Meldung (wer so heisst, ist im
Rudel dabei); andersherum haette ein einziger Zugang den Ruf ans Team
stillschweigend abgeschaltet.
istTeamDogi() NEU IN workspace.js
---------------------------------
Derselbe Ausdruck ("admin oder TEAM_DOGI_ROLLEN") stand dort dreimal
wortgleich: siehtModis, kanaeleFuer, kategorienFuer. Drei gleiche
Ausdruecke sind drei Gelegenheiten, dass einer beim naechsten
Rollenzuschnitt nicht mitgeht. Jetzt eine Stelle, die drei benutzen.
DIE NACHRICHT MERKT SICH DEN RUF (Spalte chat_nachrichten.rudel)
----------------------------------------------------------------
Beim Lesen muesste der Browser sonst wissen, ob der Absender es DAMALS
durfte. Er kennt nur dessen heutige Rolle -- wechselt jemand die Rolle,
verschwaende die Hervorhebung rueckwirkend aus einem Satz von vorletzter
Woche. Und es waere die zweite Rechnung ueber dieselbe Frage.
Vorgabe 0; alle alten Nachrichten haben kein Rudel gerufen, und das ist
keine Annahme, sondern eine Tatsache: Das Wort gab es noch nicht.
DIE MELDUNG SAGT, WAS LOS IST
-----------------------------
"X hat das Rudel gerufen", nicht "X hat dich erwaehnt" -- letzteres
stimmt beim Rudel nicht, und wer dreimal liest, dass er gemeint sei,
und jedes Mal merkt, dass es alle betraf, glaubt beim vierten Mal auch
dem echten nicht mehr. Auf DEMSELBEN Schalter wie die Erwaehnung: Ein
vierter Schalter waere der, den jemand abschaltet und der dann genau im
wichtigen Moment fehlt. Jeder Ruf steht im Protokoll (chat_rudel) --
"@rudel wird zu oft benutzt" soll eine Zahl sein koennen, kein Gefuehl.
DIE FARBE WIRD ABGELEITET, NICHT GESETZT
----------------------------------------
Eine Nachricht liegt in der Blase, deren Farbe ihr Absender AUSGESUCHT
hat -- siebzehn Moeglichkeiten. Eine feste Farbe darauf ist eine Wette,
und genau die habe ich am 22.09. beim Loeschknopf verloren (1,91:1 auf
Babyblau, gemessen, nachdem es live war). Die Marke nimmt deshalb
`--blase-text` -- die Schrift, die schriftFuer() fuer DIESE Blase mit
mindestens 7:1 ausgerechnet hat. Unterschieden wird ueber Form statt
Farbton: Toenung, Kante, Gewicht 700, ein Zeichen davor. In der
Auswahlliste darf es einen eigenen Ton haben -- sie liegt auf der
Flaeche des Hauses, deren Farbe feststeht.
DER VORSCHLAG ERSCHEINT NUR, WO ER ETWAS BEWIRKT
------------------------------------------------
In einem Zweier-Gespraech mit einem Creator ist ausser mir niemand aus
dem Team. `darf_rudel` fragt deshalb beides: darf ich, und sitzt hier
noch jemand aus dem Team. Dieselbe Ueberlegung wie beim eigenen Namen,
den die Liste auch nicht anbietet. Die Schranke beim Schreiben haengt
nicht daran -- wer es von Hand tippt, ruft eben niemanden.
GEPRUEFT
--------
pruef-erwaehnung: 119 Pruefungen, 0 Fehler (vorher 66).
Neu darin, und die zweite ist die wichtigere:
* der Modi ruft im Treff genau das Team -- die Liste wird aus der
Besetzung ABGELEITET, nicht abgeschrieben
* der Gast im selben Raum ruft NICHTS: kein Eintrag, kein Merkmal,
kein Vorschlag. Ohne diese Pruefung stuende Filipes "nur die
modis, rechte hand, linke hand und dogfather" bloss im Kommentar
* beide Fassungen der Regel (Server und Browser) an 14 zusaetzlichen
Proben nebeneinander, mit beiden Rechten -- samt Gegenprobe, dass
der Vergleich einen Unterschied ueberhaupt sehen kann
* am Bildschirm: der Ruf steht oben in der Liste, sagt daneben, was
er bedeutet, Enter setzt ihn ein, und im Satz ist er an Gewicht
und Rahmen erkennbar -- nicht nur an der Farbe
Beim Bauen gemessen statt vermutet: Ein Scout und ein Modi kommen gar
nicht in denselben Raum (403, Haeusertrennung) -- deshalb prueft der
Verhaltenstest im Treff. Und ein Gast meldet sich nur mit
Altersbestaetigung an (400 ohne).
Die Nachtruhe des Treffs wird in dieser Pruefung abgeschaltet (gleiche
Stunden = keine Nachtruhe). Sonst waere sie zwischen Mitternacht und
sechs rot und danach gruen -- ein Test, der die Wanduhr misst.
pruef-chat, pruef-treffchat (110), pruef-chat-kanaele (81),
pruef-chat-ausbau (64): alle unveraendert gruen.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+136
-8
@@ -45,9 +45,10 @@ import {
|
||||
db, protokolliere, echteIp, sitzungLesen, istDogFather,
|
||||
schreibbareIds, darfSchreibenMit, ROLLEN_SORTIERUNG, DATEN_ORDNER,
|
||||
fuehrtTeamDogi, MODI_KATEGORIEN, ROLLEN_GRUPPE, TEAM_DOGI_ROLLEN,
|
||||
istTeamDogi,
|
||||
} from "./workspace.js";
|
||||
|
||||
import { erwaehnteIds } from "./chat-erwaehnung.js";
|
||||
import { erwaehnteIds, rudelGerufen, RUDEL_WORT } from "./chat-erwaehnung.js";
|
||||
import { KATALOG, ERLAUBT, VORSCHLAG, FAVORITEN_MAX } from "./workspace-reaktionen.js";
|
||||
|
||||
/* Der Treff-Chat: eigener Raum, eigene Nachtruhe, keine Anrufe.
|
||||
@@ -1296,7 +1297,7 @@ chatRouter.get("/workspace/api/chat/raeume/:id/nachrichten", (req, res) => {
|
||||
const reihen = db().prepare(`
|
||||
SELECT n.id, n.person_id, n.text, n.erstellt, n.weg_am, p.name AS von, p.rolle,
|
||||
p.bild AS von_bild, p.chat_kachel AS von_kachel,
|
||||
n.antwort_auf, n.angeheftet_am,
|
||||
n.antwort_auf, n.angeheftet_am, n.rudel,
|
||||
n.anhang_name, n.anhang_art, n.anhang_typ, n.anhang_groesse,
|
||||
n.anhang_breite, n.anhang_hoehe,
|
||||
a.text AS zitat_text, a.weg_am AS zitat_weg, ap.name AS zitat_von,
|
||||
@@ -1368,6 +1369,31 @@ chatRouter.get("/workspace/api/chat/raeume/:id/nachrichten", (req, res) => {
|
||||
waere eine Auskunft ueber das Haus an jeden Leser. Und es
|
||||
waere eine zweite Wahrheit neben der Schranke im Server. */
|
||||
darf_alles_loeschen: darfJedeNachrichtLoeschen(req.person),
|
||||
/* DARF ICH DAS RUDEL RUFEN? (23.09.2026)
|
||||
|
||||
Vom Server, aus derselben Funktion wie die Schranke beim
|
||||
Schreiben (istTeamDogi). Die Oberflaeche koennte
|
||||
`ich.rolle === 'modi' || ...` vergleichen -- das waere eine
|
||||
zweite Wahrheit, und assets/js/chat.js bekommt jeder, der die
|
||||
Seite oeffnet. Ein Ja/Nein verraet nichts ueber das Haus.
|
||||
|
||||
DAS WORT KOMMT MIT. Stuende "rudel" im Browser fest, hiesse es
|
||||
nach einer Umbenennung an einer Stelle anders als an der
|
||||
anderen -- und der Vorschlag beim Tippen setzte etwas ein, das
|
||||
der Server nicht mehr erkennt. */
|
||||
/* UND OB ER HIER ueberhaupt jemanden erreichen wuerde. In einem
|
||||
Zweier-Gespraech mit einem Creator ist ausser mir niemand aus
|
||||
dem Team -- der Vorschlag "rudel, alle im Team" waere dort ein
|
||||
Eintrag, der nichts tut. Dieselbe Ueberlegung wie beim eigenen
|
||||
Namen, den die Liste auch nicht anbietet.
|
||||
|
||||
DIE SCHRANKE BEIM SCHREIBEN HAENGT NICHT DARAN. Wer den Ruf
|
||||
von Hand tippt, ruft eben niemanden -- genau wie ein "@Anna",
|
||||
das Anna nicht beantwortet. Verboten ist es nicht, es ist nur
|
||||
keine Hilfe, es vorzuschlagen. */
|
||||
darf_rudel: istTeamDogi(req.person)
|
||||
&& leute.some((t) => t.id !== req.person.id && istTeamDogi(t)),
|
||||
rudel_wort: RUDEL_WORT,
|
||||
angeheftet: angeheftet.map((a) => ({
|
||||
id: a.id,
|
||||
von: a.von || "Gelöscht",
|
||||
@@ -1405,6 +1431,10 @@ chatRouter.get("/workspace/api/chat/raeume/:id/nachrichten", (req, res) => {
|
||||
erstellt: n.erstellt,
|
||||
selbst: n.person_id === req.person.id,
|
||||
angeheftet: !!n.angeheftet_am,
|
||||
/* WURDE HIER DAS RUDEL GERUFEN? Steht an der Nachricht, weil
|
||||
der Browser sonst die damalige Rolle des Absenders kennen
|
||||
muesste -- siehe die Spalte in workspace.js. */
|
||||
rudel: !!n.rudel,
|
||||
/* Das Zitat wird GEKUERZT geschickt, nicht ganz: Es ist ein
|
||||
Hinweis auf die Stelle, nicht ihre Wiederholung. Und eine
|
||||
zurueckgenommene Nachricht bleibt auch im Zitat
|
||||
@@ -1530,6 +1560,11 @@ chatRouter.post("/workspace/api/chat/raeume/:id/nachrichten", gleicheHerkunft,
|
||||
zurueckgenommen: !!zitat.weg_am,
|
||||
} : null,
|
||||
anhang: null,
|
||||
/* Wird gleich gesetzt, wenn das Rudel gerufen wurde. Steht
|
||||
hier schon drin, damit das Feld in JEDER Nachricht vorkommt
|
||||
-- eine Eigenschaft, die mal da ist und mal nicht, ist die,
|
||||
die im Browser irgendwann `undefined` heisst. */
|
||||
rudel: false,
|
||||
};
|
||||
|
||||
/* ==== WER IST MIT "@" GEMEINT? (20.09.2026) =====================
|
||||
@@ -1548,17 +1583,73 @@ chatRouter.post("/workspace/api/chat/raeume/:id/nachrichten", gleicheHerkunft,
|
||||
Die Regeln stehen in chat-erwaehnung.js, samt Begruendung je
|
||||
Regel. Sie sind bewusst ohne Abhaengigkeiten, damit die
|
||||
Pruefung sie lesen kann, ohne einen Server hochzufahren. */
|
||||
/* ==== "@RUDEL" -- ALLE AUF EINMAL (23.09.2026) =================
|
||||
|
||||
Filipe: „dan will ich auch dass nur die modis, rechte hand,
|
||||
linke hand und dogfather, alle auch auf einmal markieren
|
||||
koennen im chat mit einem, @rudel ,dann sollen alle eine
|
||||
benarichtigung bekommen."
|
||||
|
||||
WER DARF, WIRD HIER ENTSCHIEDEN -- nicht im Browser und nicht
|
||||
in chat-erwaehnung.js. Der Browser schickt nur Text; die Regeln
|
||||
haben keine Abhaengigkeiten und sollen keine bekommen. Bleibt
|
||||
diese Stelle, und sie fragt die eine Funktion des Hauses:
|
||||
istTeamDogi(). Schreibt ein Creator "@rudel", passiert nichts
|
||||
-- kein Ruf, kein Eintrag, keine Hervorhebung. */
|
||||
const darfRudel = istTeamDogi(req.person);
|
||||
const alleLeute = teilnehmerVon(raumId);
|
||||
const erwaehnt = erwaehnteIds(text, alleLeute)
|
||||
const rudel = rudelGerufen(text, alleLeute, { rudel: darfRudel });
|
||||
|
||||
/* WEN DER RUF ERREICHT: die Leute IN DIESEM RAUM, die zu Team
|
||||
Dogi gehoeren -- nicht jeden im Raum.
|
||||
|
||||
Das Wort sagt es selbst. Ein Rudel ist das Team, und Filipe
|
||||
nennt in einem Atemzug dieselben vier Gruppen, die es auch
|
||||
rufen duerfen. In einem reinen Team-Kanal ist das jeder
|
||||
Anwesende, also genau "alle auf einmal".
|
||||
|
||||
DER UNTERSCHIED WIRD IM TREFF SICHTBAR, und dort haette die
|
||||
andere Lesart wehgetan: Dort sitzt die Community. "@rudel"
|
||||
haette zweihundert Zuschauer gleichzeitig geweckt -- und beim
|
||||
zweiten Mal haetten sie die Benachrichtigungen abgeschaltet.
|
||||
Genau davor warnt der erste Absatz dieser Datei seit dem
|
||||
20.09. Ein Moderator, der im Treff "@rudel" schreibt, erreicht
|
||||
damit seine Kollegen; die Zuschauer lesen es einfach mit. */
|
||||
const ausDemRudel = rudel
|
||||
? alleLeute.filter((t) => istTeamDogi(t)).map((t) => t.id)
|
||||
: [];
|
||||
|
||||
const erwaehnt = [...new Set([
|
||||
...erwaehnteIds(text, alleLeute, { rudel: darfRudel }),
|
||||
...ausDemRudel,
|
||||
])]
|
||||
/* SICH SELBST ANSPRECHEN LOEST NICHTS AUS. Man schreibt den
|
||||
eigenen Namen durchaus ("@Anna hat recht, ich auch") -- eine
|
||||
Meldung an sich selbst waere Unsinn. */
|
||||
Meldung an sich selbst waere Unsinn. Beim Rudel genauso: Wer
|
||||
das Team ruft, ruft nicht sich. */
|
||||
.filter((pid) => pid !== req.person.id);
|
||||
if (erwaehnt.length) {
|
||||
const einf = d.prepare(
|
||||
"INSERT OR IGNORE INTO chat_erwaehnungen (nachricht_id, person_id) VALUES (?,?)");
|
||||
for (const pid of erwaehnt) einf.run(id, pid);
|
||||
}
|
||||
if (rudel) {
|
||||
/* DIE NACHRICHT MERKT SICH, DASS DAS RUDEL GERUFEN WURDE.
|
||||
Daraus entsteht beim Lesen die Hervorhebung -- ohne dass der
|
||||
Browser die Rolle des Absenders von damals nachschlagen
|
||||
muesste. Begruendung steht bei der Spalte in workspace.js. */
|
||||
d.prepare("UPDATE chat_nachrichten SET rudel = 1 WHERE id = ?").run(id);
|
||||
nachricht.rudel = true;
|
||||
/* INS PROTOKOLL. Ein Ruf ans ganze Team ist der eine Handgriff
|
||||
im Chat, der alle gleichzeitig unterbricht. Wer ihn benutzt,
|
||||
soll das nicht heimlich tun koennen -- nicht als Misstrauen,
|
||||
sondern weil "@rudel wird zu oft benutzt" sonst ein Gefuehl
|
||||
bleibt statt einer Zahl. */
|
||||
protokolliere("chat_rudel", {
|
||||
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
|
||||
detail: `#${id} an ${erwaehnt.length} im Raum ${raumId}`.slice(0, 120),
|
||||
});
|
||||
}
|
||||
|
||||
/* Sofort an alle, die gerade zusehen -- und per Benachrichtigung
|
||||
an die, die es nicht tun. Beides läuft über chatEreignis(),
|
||||
@@ -1570,7 +1661,7 @@ chatRouter.post("/workspace/api/chat/raeume/:id/nachrichten", gleicheHerkunft,
|
||||
abschaltet. */
|
||||
const empfaenger = alleLeute.filter((t) => t.id !== req.person.id);
|
||||
chatEreignis(raumId, nachricht, empfaenger.map((t) => t.id), "neu",
|
||||
new Set(erwaehnt));
|
||||
new Set(erwaehnt), rudel);
|
||||
|
||||
res.status(201).json({ nachricht });
|
||||
} catch (fehler) {
|
||||
@@ -2481,7 +2572,7 @@ const zuschauer = new Map();
|
||||
* bekommt was" gehoert an EINE Stelle.
|
||||
*/
|
||||
function chatEreignis(raumId, nachricht, benachrichtigen, art = "neu",
|
||||
erwaehnte = new Set()) {
|
||||
erwaehnte = new Set(), rudel = false) {
|
||||
/* 1. An alle offenen Verbindungen der Beteiligten. */
|
||||
const leute = teilnehmerVon(raumId).map((t) => t.id);
|
||||
for (const personId of leute) {
|
||||
@@ -2520,8 +2611,19 @@ function chatEreignis(raumId, nachricht, benachrichtigen, art = "neu",
|
||||
Wortlaut UND Art -- und die Art kann jeder einzeln abschalten.
|
||||
Wer den Chat stumm stellt, weil dort viel geredet wird, will
|
||||
trotzdem wissen, wenn ihn jemand direkt anspricht. */
|
||||
/* DREI STUFEN STATT ZWEI (23.09.2026): nur dabei -- angesprochen
|
||||
-- das ganze Rudel gerufen. Der Unterschied steht im WORTLAUT,
|
||||
nicht in einer zweiten Meldung: "Anna hat das Rudel gerufen"
|
||||
sagt sofort, dass es alle betrifft und nicht nur mich.
|
||||
|
||||
AUF DEMSELBEN SCHALTER WIE DIE ERWAEHNUNG, mit Absicht (siehe
|
||||
meldeNeueNachricht). Ein Ruf ans Team IST eine Erwaehnung -- nur
|
||||
eben von allen. Ein eigener vierter Schalter waere der, den
|
||||
jemand abschaltet und der dann genau im wichtigen Moment fehlt. */
|
||||
void meldeNeueNachricht(personId, raumId, nachricht,
|
||||
art === "neu" && erwaehnte.has(personId) ? "erwaehnung" : art);
|
||||
art === "neu" && erwaehnte.has(personId)
|
||||
? (rudel ? "rudel" : "erwaehnung")
|
||||
: art);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2591,6 +2693,27 @@ export function pushTextFuer(art, nachricht, raumId) {
|
||||
};
|
||||
}
|
||||
|
||||
/* ---- DAS GANZE RUDEL (23.09.2026) ----------------------------
|
||||
|
||||
Filipe: „dann sollen alle eine benarichtigung bekommen."
|
||||
|
||||
WARUM NICHT EINFACH „hat dich erwähnt": Weil es nicht stimmt.
|
||||
Beim Rudel ist man nicht gemeint, sondern mitgemeint -- und wer
|
||||
dreimal „hat dich erwähnt" liest und jedes Mal feststellt, dass
|
||||
es alle betraf, glaubt beim vierten Mal auch dem echten nicht
|
||||
mehr. Der Wortlaut sagt deshalb, was los ist.
|
||||
|
||||
ES IST TROTZDEM DIESELBE ART von Meldung (chat_erwaehnung, siehe
|
||||
meldeNeueNachricht) -- ein Ruf ans Team ist eine Erwähnung, nur
|
||||
eben von allen. */
|
||||
if (art === "rudel") {
|
||||
return {
|
||||
titel: `${wer} hat das Rudel gerufen`,
|
||||
text: String(nachricht.text || "").slice(0, 120),
|
||||
ziel,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
titel: `Nachricht von ${wer}`,
|
||||
/* Der Anfang der Nachricht steht in der Meldung -- eine
|
||||
@@ -2636,7 +2759,12 @@ async function meldeNeueNachricht(personId, raumId, nachricht, art = "neu") {
|
||||
Wer den lauten Chat stumm stellt, verliert damit weder Anrufe
|
||||
noch die Faelle, in denen ihn jemand direkt anspricht. */
|
||||
const pushArt = art === "anruf" ? "anruf"
|
||||
: art === "erwaehnung" ? "chat_erwaehnung"
|
||||
/* Der Ruf ans Rudel laeuft ueber DENSELBEN Schalter wie die
|
||||
Erwaehnung. Kein vierter Schalter: Wer "@rudel" stummschaltet,
|
||||
schaltet den Fall stumm, in dem das ganze Team gebraucht wird
|
||||
-- und genau dafuer gibt es ihn. Wem es zu viel wird, der sagt
|
||||
es demjenigen, der ruft; im Protokoll steht, wer das war. */
|
||||
: (art === "erwaehnung" || art === "rudel") ? "chat_erwaehnung"
|
||||
: "chat_nachricht";
|
||||
await benachrichtige(personId, pushArt, pushTextFuer(art, nachricht, raumId));
|
||||
} catch (fehler) {
|
||||
|
||||
Reference in New Issue
Block a user