Chat: Gruender duerfen ihre Gruppe oder ihren Kanal aufloesen — und der Umschalter erklaert sich

Zwei Wuensche von Filipe, beide am Neu-Fenster.

1) "ich will eine bessere erklaerung dafuer bitte."
Neben dem Umschalter "Gespraech | Kanal" stand ein Satz ueber das
ANTIPPEN von Personen -- also ueber den naechsten Schritt, nicht ueber
die Wahl, die gerade ansteht. Wer die beiden Woerter zum ersten Mal
sieht, erfuhr nirgends, was sie bedeuten.

Der Unterschied ist nicht "wenige/viele Leute", sondern WONACH der Raum
benannt ist: ein Gespraech nach den Menschen darin, ein Kanal nach
einem Thema. Daran haengt alles Weitere -- dass es einen Kanal je
Zustaendigkeit nur einmal gibt (eindeutiger Index, nachgesehen), dass
sein Name festliegt, dass die Teamleitung immer dabei ist
(kanaeleAngleichen, nachgesehen) und dass Leute wechseln koennen, ohne
dass der Raum ein anderer wird. Genau das steht jetzt da, und nichts
davon ist behauptet.

2) "die person die ihn oeffnet soll auch das recht haben das zu
loeschen und so dass es dan fuer jeden geloescht ist. aber nur die
person die es gruendet."

Ein EIGENER Weg (/ganz), kein Zusatzfeld am bestehenden. Es gibt jetzt
zwei Loeschknoepfe nebeneinander, und sie tun etwas sehr Verschiedenes:
Wegraeumen ist nur bei mir, Aufloesen ist fuer alle und endgueltig. Ein
vergessenes Feld waere genau dieser Unterschied gewesen. Aus demselben
Grund ein anderes Zeichen und eine eigene Warnfarbe -- zwei gleich
aussehende Papierkoerbe waeren eine Falle.

Nur der Gruender, woertlich: nicht die Teamleitung, nicht DogFather,
nicht wer `leitung` in der Gruppe hat. Nicht bei Zweier-Gespraechen --
dort gibt es keinen Gruender, und "niemand nimmt einem anderen die
Unterhaltung weg" gilt weiter.

Reihenfolge beim Loeschen ist nicht beliebig: erst das Live-Ereignis
(chatEreignis liest die Teilnehmer aus der Tabelle -- danach waere die
Liste leer), dann die Zeilen in EINER Transaktion, dann die Anhaenge
von der Platte. Umgekehrt haetten wir bei einem Ruecklauf Nachrichten,
die auf geloeschte Dateien zeigen.

WAS DIE PRUEFUNG GEFUNDEN HAT, BEVOR ES JEMAND GEMERKT HAETTE: Der
Knopf blieb unsichtbar, obwohl das Recht stimmte. Die Oberflaeche holt
den offenen Raum aus dem Nachrichten-Weg, nicht aus der Raumliste --
zwei Wege, ein Raumobjekt, und nur einer kannte das neue Feld. Die
Regel steht jetzt in darfAufloesen() und wird von allen dreien
benutzt: Liste, Nachrichten-Weg und der Loeschweg selbst.

Gefunden hat das die Pruefung, weil sie den KNOPF misst und nicht das
Recht dahinter. Haette sie nur `darf_aufloesen` geprueft, waere sie
gruen gewesen und der Knopf nie erschienen.

NEU: server/pruef-chat-aufloesen.mjs (59 Pruefungen). Sie misst am
BESTAND, nicht an der Antwort: ob der Raum wirklich aus der Datenbank
weg ist, ob keine Teilnehmerzeile liegen blieb, ob der Anhang von der
Platte verschwand -- und mit Gegenprobe, dass der Anhang-Ordner selbst
stehen bleibt. Ohne die waere "Datei ist weg" auch dann gruen, wenn es
sie nie gab; genau das ist beim ersten Lauf passiert (der Upload lief
ins 415, weil ich ihn als Formular statt roh geschickt hatte).

Dazu: Wegraeumen ist NICHT Aufloesen (Ben raeumt weg, Cem hat alles
noch), ein Zweier-Gespraech laesst sich gar nicht aufloesen, ein
Aussenstehender bekommt 404 statt 403, ein zweiter Versuch findet
nichts, und die Zustaendigkeit eines aufgeloesten Kanals wird wieder
frei (sonst haette der eindeutige Index sie dauerhaft blockiert).

Nebenbei: Die drei Kopfknoepfe schoben sich jeder einzeln mit
`margin-left: auto` nach rechts. Bei zwei sichtbaren teilen sich zwei
auto-Raender den freien Platz und reissen sie auseinander -- und WELCHE
sichtbar sind, entscheidet der Server. Jetzt schiebt ein Behaelter
einmal, die Knoepfe stehen beieinander, egal wie viele es sind.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-11 15:22:09 +02:00
co-authored by Claude Opus 5
parent 14ed467928
commit f23260dcbb
28 changed files with 1156 additions and 303 deletions
+112 -13
View File
@@ -125,6 +125,9 @@
Ein Knopf, der beim Druecken "darfst du nicht" sagt, ist eine
Enttaeuschung mit Ansage. */
$('neu-art').hidden = !darfKanal;
/* Abgeleitet, nicht zweimal entschieden: Die Erklaerung gehoert
genau dann hin, wenn es auch etwas zu entscheiden gibt. */
$('neu-erklaerung').hidden = $('neu-art').hidden;
if (!darfKanal) neuArt = 'gespraech';
} catch { return; }
raeumeZeichnen();
@@ -327,6 +330,12 @@
$('chat').dataset.ansicht = 'verlauf';
/* Erst jetzt gibt es etwas zum Wegräumen. */
$('gespraech-weg').hidden = false;
/* AUFLOESEN: nur beim Gruender, und nur bei Gruppe oder Kanal.
Die Antwort kommt vom Server (`darf_aufloesen`) -- hier wird
nichts nachgerechnet. Der Browser kennt `erstellt_von` gar
nicht, und eine zweite Rechenvorschrift waere die, die
irgendwann von der ersten abweicht. */
$('raum-aufloesen').hidden = !offen.darf_aufloesen;
/* Ein frisch geoeffnetes Gespraech steht immer am Ende -- man will
das Letzte sehen, nicht das Erste. */
@@ -996,6 +1005,28 @@
raeumeLaden();
});
/* DER RAUM IST AUFGELOEST (11.09.2026).
Kommt beim Gruender genauso an wie bei allen anderen -- er
braucht keinen Sonderweg, und dadurch gibt es keinen zweiten
Ablauf, der irgendwann anders aussieht.
Wer ihn gerade offen hat, bekommt einen Satz dazu. Ohne den
spraenge die Seite wortlos auf die Liste zurueck, und man
haelt es fuer einen Fehler. Wer ihn NICHT offen hat, merkt nur,
dass ein Eintrag aus der Liste verschwindet -- das reicht. */
strom.addEventListener('raum-weg', (e) => {
let n;
try { n = JSON.parse(e.data); } catch { return; }
if (offen && n.raum_id === offen.id) {
verlaufSchliessen();
melde('Dieses Gespräch wurde von der Person aufgelöst, die es '
+ 'angelegt hat.');
setTimeout(() => melde(''), 8000);
}
raeumeLaden();
});
/* ANGEHEFTET ODER GELOEST -- alle im Raum sehen es sofort.
Betroffen ist nur, wer denselben Raum offen hat; sonst genuegt
die Liste. */
@@ -1140,11 +1171,17 @@
$('kanal-zeile').hidden = art !== 'kanal' || !!kanalUmbau;
$('neu-titel').textContent = kanalUmbau ? 'Wer ist im Kanal?'
: art === 'kanal' ? 'Neuer Kanal' : 'Neues Gespräch';
/* Der Satz sagt, was JETZT zu tun ist -- der Unterschied zwischen
den beiden Arten steht darueber in #neu-erklaerung und muss hier
nicht noch einmal stehen. Zwei Texte fuer dieselbe Auskunft
laufen auseinander, sobald einer geaendert wird. */
$('neu-unter').textContent = kanalUmbau
? 'Antippen, um jemanden hinein- oder herauszunehmen. Die Leitung bleibt immer dabei.'
: art === 'kanal'
? 'Ein Kanal je Zuständigkeit. Der Name kommt aus der Kategorie – so gibt es ihn nur einmal.'
: 'Eine Person antippen, um ihr zu schreiben. Mehrere auswählen ergibt eine Gruppe.';
? 'Zuständigkeit wählen – der Rest ergibt sich daraus. Leute kannst du gleich '
+ 'mitnehmen oder später hinzufügen.'
: 'Eine Person antippen, um ihr zu schreiben. Mehrere auswählen ergibt eine '
+ 'Gruppe – die bekommt einen Namen und bleibt bestehen.';
wahlGeaendert();
}
@@ -1625,6 +1662,28 @@
Die Rückfrage sagt ausdrücklich, was NICHT passiert. "Wirklich
löschen?" liesse offen, ob der andere seinen Verlauf verliert --
und das ist genau die Frage, die man in dem Moment hat. */
/* ZURUECK AUF "nichts offen" -- an EINER Stelle (11.09.2026).
Diese zehn Zeilen standen beim Wegraeumen. Mit dem Aufloesen und
dem Ereignis "der Raum ist weg" braeuchte es sie dreimal, und die
dritte Abschrift waere die, in der eine Zeile fehlt: ein Knopf,
der stehen bleibt, oder ein Entwurf zu einem Raum, den es nicht
mehr gibt. */
function verlaufSchliessen() {
if (offen) entwurfWeg(offen.id);
offen = null;
nachrichten = [];
$('schreiben').hidden = true;
$('gespraech-weg').hidden = true;
$('raum-aufloesen').hidden = true;
$('kanal-leute').hidden = true;
$('verlauf-titel').textContent = 'Wähle ein Gespräch';
$('verlauf-unter').textContent = '';
$('verlauf-leer').hidden = false;
$('chat').dataset.ansicht = 'liste';
verlaufZeichnen();
history.replaceState(null, '', 'chat.html');
}
$('gespraech-weg').addEventListener('click', async () => {
if (!offen) return;
const wer = offen.name;
@@ -1636,22 +1695,62 @@
try {
const a = await hole(`/workspace/api/chat/raeume/${offen.id}`, { method: 'DELETE' });
if (!a.ok) { melde('Ging nicht.'); return; }
entwurfWeg(offen.id);
offen = null;
nachrichten = [];
$('schreiben').hidden = true;
$('gespraech-weg').hidden = true;
$('verlauf-titel').textContent = 'Wähle ein Gespräch';
$('verlauf-unter').textContent = '';
$('verlauf-leer').hidden = false;
$('chat').dataset.ansicht = 'liste';
verlaufZeichnen();
history.replaceState(null, '', 'chat.html');
verlaufSchliessen();
await raeumeLaden();
melde('');
} catch { melde('Keine Verbindung.'); }
});
/* =====================================================================
FUER ALLE AUFLOESEN (11.09.2026)
Filipe: "die person die ihn oeffnet soll auch das recht haben das
zu loeschen und so dass es dan fuer jeden geloescht ist. aber nur
die person die es gruendet."
DIE RUECKFRAGE NENNT ZAHLEN, KEINE FLOSKEL. "Wirklich loeschen?"
beantwortet die Frage nicht, die man in dem Moment hat -- naemlich
WAS genau verschwindet und bei WEM. Deshalb stehen der Name, die
Zahl der Beteiligten und das Wort "endgueltig" darin.
Und sie sagt ausdruecklich, was der Unterschied zum Knopf daneben
ist. Zwei Loeschknoepfe nebeneinander sind sonst eine Falle. */
$('raum-aufloesen').addEventListener('click', async () => {
if (!offen || !offen.darf_aufloesen) return;
const wasIstEs = offen.art === 'kanal' ? 'Kanal' : 'Gruppe';
const andere = Math.max(0, (offen.teilnehmer?.length || 1) - 1);
if (!window.confirm(
`${wasIstEs} „${offen.name}" für alle auflösen?\n\n`
+ `Der ganze Verlauf wird gelöscht – auch bei ${andere === 1
? 'der anderen Person' : `den ${andere} anderen`}, samt aller `
+ 'Anhänge. Das lässt sich nicht rückgängig machen.\n\n'
+ 'Wenn du sie nur bei dir loswerden willst, nimm den Knopf '
+ 'daneben – dann behalten die anderen alles.')) return;
const knopf = $('raum-aufloesen');
knopf.disabled = true;
try {
const a = await hole(`/workspace/api/chat/raeume/${offen.id}/ganz`,
{ method: 'DELETE' });
if (!a.ok) {
/* Der Server ist die Wahrheit, nicht der Knopf. Sagt er nein,
steht der Grund da -- nicht "Ging nicht." */
const d = await a.json().catch(() => ({}));
melde(d.fehler === 'nur_gruender'
? `Nur wer ${wasIstEs === 'Kanal' ? 'den Kanal' : 'die Gruppe'} `
+ 'angelegt hat, kann sie auflösen.'
: 'Ging nicht.');
return;
}
const d = await a.json().catch(() => ({}));
verlaufSchliessen();
await raeumeLaden();
melde(`${wasIstEs} aufgelöst – ${d.nachrichten ?? 0} Nachrichten `
+ 'für alle gelöscht.', 'gut');
setTimeout(() => melde(''), 6000);
} catch { melde('Keine Verbindung.'); }
finally { knopf.disabled = false; }
});
/* Zurück an den Bildschirm: nachsehen, was inzwischen kam, und melden,
dass jetzt wirklich jemand hinsieht. */
document.addEventListener('visibilitychange', () => {