Kategorie-Kanaele und angeheftete Ankuendigungen (Kapitel 7.2)
Die vier Saetze aus dem Anforderungsdokument, der Reihe nach: Team- Gruppenchat, Kategorie-Kanaele mit Zugriff fuer Owner und rechte Hand, private 1:1-Chats OHNE diesen Zugriff, und Pin-Nachrichten an alle. EIN KANAL IST KEINE VIERTE TABELLE, sondern eine dritte Art Raum (`art = 'kanal'` neben 'direkt' und 'gruppe'). Damit gilt fuer ihn ohne eine einzige neue Zeile alles, was schon da ist: Verlauf, Anhaenge, Suche, Ungelesen-Zaehler, Live-Strom, Wegraeumen. Eine eigene Tabelle haette all das ein zweites Mal gebraucht -- und die zweite Fassung waere die gewesen, in der die Zugriffsregel fehlt. Welche Zustaendigkeit, kommt aus MODI_KATEGORIEN -- derselben Liste, aus der auch die Aufgaben ihre Kategorie nehmen. Der NAME kommt aus der Kategorie und ist kein freies Feld: "Clipping" neben "Clipping-Team" waeren zwei halbe Verlaeufe, und man merkt es erst, wenn jemand die Antwort im falschen sucht. Ein eindeutiger Teilindex haelt das auch dann fest, wenn zwei Anfragen im selben Augenblick ankommen. DIE ZUGRIFFSREGEL STEHT IN istDrin() -- der Funktion, durch die alle sieben lesenden und schreibenden Wege gehen. In den Routen stuende sie in sechs davon und in der siebten nicht. Sie gilt AUSDRUECKLICH nur fuer 'kanal': Zweier-Gespraeche bleiben zu, auch fuer DogFather (so steht es im Dokument), und Gruppen ebenfalls -- wer eine Gruppe aufmacht, hat sich fuer einen geschlossenen Kreis entschieden, und den nachtraeglich still zu oeffnen waere das Gegenteil dessen, was er getan hat. Wenn Filipe das anders will, ist es eine Zeile -- aber es waere seine Entscheidung und muesste fuer die Beteiligten SICHTBAR sein. istDrin() nimmt dafuer die PERSON statt ihrer Nummer und wirft bei einer Nummer einen Fehler, statt stillschweigend "nein" zu antworten. HOECHSTENS DREI ANKUENDIGUNGEN je Raum. Nicht eine (Regeln, Live-Plan und Frist muessen gleichzeitig oben stehen koennen) und nicht beliebig viele -- eine Pinnwand, die scrollt, ist ein zweiter Verlauf. Der Aushang hat eine EIGENE Abfrage, weil der Verlauf nur 200 Zeilen liefert: Eine Ansage von vorletzter Woche waere sonst genau dann weg, wenn sie am laengsten oben stehen sollte. Wird die Nachricht zurueckgenommen, faellt sie ab UND gibt den Platz frei. DREI DINGE, DIE ERST DAS HINSEHEN GEZEIGT HAT: Der frisch angelegte Kanal hatte zwei Leute statt vier. Die Personenauswahl zeichnete nach der Rollenfolge aus bereiche.js -- wer dort nicht steht, wurde NICHT GEZEICHNET. Team Dogi steht dort nicht und darf es auch nicht (der Rollenname gehoert in keine Datei, die jeder herunterlaedt). Folge: DogFather konnte ueber die Auswahl niemandem aus seinem Team schreiben. Der Server schickt die Ueberschrift jetzt mit; der Browser braucht dafuer keinen Rollennamen. Der Kommentar, der genau davor warnte, stand die ganze Zeit darueber. Dieser Fehler war nebenbei ein Netz: Was nicht gezeichnet wird, kann auch nicht falsch gezeichnet werden. Deshalb ist jetzt gemessen, dass ein Manager und Spicy Media Team Dogi gar nicht erst geschickt bekommen -- und dabei fiel auf, dass Spicy Media in der EIGENEN Auswahl stand: Sobald es jemanden zu verbergen gibt, schreibt ohneVerborgene() aus "sieht alles" eine echte Liste, und darin steckt man selbst. Am Fuss jeder Nachricht stehen jetzt vier Handgriffe statt drei. Bei 390 px -- der haeufigsten Handybreite -- stand "kopieren" 18 px ueber der Blase, bei 320 px 75. Behoben mit `flex-wrap: wrap` und nicht mit einer Schwelle: Eine Regel, die misst, bleibt beim fuenften Handgriff richtig; eine Zahl nicht. pruef-chat-optik misst es ab jetzt. NACHGEZOGEN, was pruef-css-klassen an MEINEM letzten Commit fand: teamlage.html lud kopf.js ohne wahl.js (der Sicht-Umschalter sah aus wie aus einem anderen Programm -- kaputt war nichts, und genau deshalb faellt es niemandem auf), und .tampel__tag stand auf 11,2 px. Beides war schon gepusht, weil ich die Pruefung nicht laufen liess. pruef-chat-kanaele 73 (neu) · pruef-chat 48 · pruef-chat-ausbau 64 · pruef-chat-anhaenge 60 · pruef-chat-optik 24 -> 26 · pruef-css-klassen gruen · pruef-rollen 274 · pruef-modi-verborgen 78 · pruef-team-ampel 28 · pruef-start-ansicht gruen · pruef-modi-wortleck 5 · pruef-zwischenspeicher 21. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+143
-5
@@ -115,6 +115,21 @@ const LEITUNG = new Set(["spicy", "admin", "manager"]);
|
||||
export const istLeitung = (person) => !!person && LEITUNG.has(person.rolle);
|
||||
export const istDogFather = (person) => !!person && person.rolle === "admin";
|
||||
|
||||
/* WER FUEHRT TEAM DOGI? (10.09.2026)
|
||||
*
|
||||
* DogFather und seine rechte Hand -- und sonst niemand. Spicy Media und
|
||||
* die Manager stehen bewusst NICHT darin: Sie fuehren die Agentur, nicht
|
||||
* das Team.
|
||||
*
|
||||
* WARUM DAS HIER STEHT UND NICHT DORT, WO ES GEBRAUCHT WIRD: Es wurde
|
||||
* schon zweimal gebraucht -- im Eingang (`darfEingang`) und jetzt bei
|
||||
* den Kategorie-Kanaelen. Beim dritten Mal waere es dreimal
|
||||
* hingeschrieben, und die dritte Abschrift ist die, die eine Rolle
|
||||
* vergisst. Der Eingang ruft ab jetzt hierher.
|
||||
*/
|
||||
export const fuehrtTeamDogi = (person) =>
|
||||
!!person && (person.rolle === "admin" || person.rolle === "hand");
|
||||
|
||||
/* =======================================================================
|
||||
SPICY MEDIA (07.09.2026)
|
||||
|
||||
@@ -262,6 +277,36 @@ export const ROLLEN_NAME = {
|
||||
modi: "Modi",
|
||||
};
|
||||
|
||||
/* DIE UEBERSCHRIFT UEBER EINER PERSONENLISTE (10.09.2026).
|
||||
*
|
||||
* Sie ist NICHT dasselbe wie das Namensschild: Ueber einer Liste steht
|
||||
* die Mehrzahl ("Scouts"), am einzelnen Menschen die Einzahl ("Scout").
|
||||
* Der Browser hat diese Zuordnung in bereiche.js ebenfalls -- aber nur
|
||||
* fuer die fuenf Rollen, die dort stehen duerfen.
|
||||
*
|
||||
* WARUM SIE HIER GEBRAUCHT WIRD: In der Personenauswahl des Chats
|
||||
* wurde nach `ROLLENFOLGE` aus bereiche.js gezeichnet. Wer dort nicht
|
||||
* vorkommt, wurde nicht gezeichnet -- ohne Fehler, ohne Luecke, ohne
|
||||
* Hinweis. Team Dogi kommt dort nicht vor und darf es auch nicht (der
|
||||
* Rollenname gehoert in keine Datei, die jeder herunterlaedt). Folge:
|
||||
* DogFather konnte ueber die Auswahl niemandem aus seinem Team
|
||||
* schreiben und keinen in einen Kanal setzen. Aufgefallen ist es nicht
|
||||
* beim Lesen, sondern beim Hinsehen: Der Kanal hatte hinterher zwei
|
||||
* Leute statt vier.
|
||||
*
|
||||
* Jetzt schickt der Server die Ueberschrift mit. Der Browser braucht
|
||||
* dafuer keinen Rollennamen zu kennen -- er bekommt einen Text.
|
||||
*/
|
||||
export const ROLLEN_GRUPPE = {
|
||||
...ROLLEN_NAME,
|
||||
scout: "Scouts",
|
||||
/* Beide unter EINER Ueberschrift: In der Auswahl sucht man einen
|
||||
Menschen, keine Rangordnung -- und zwei Ueberschriften mit je
|
||||
einem Namen darunter waeren mehr Gliederung als Inhalt. */
|
||||
hand: "Team Dogi",
|
||||
modi: "Team Dogi",
|
||||
};
|
||||
|
||||
/* =====================================================================
|
||||
DIE KACHELN EINES MODIS (10.09.2026)
|
||||
|
||||
@@ -1131,6 +1176,40 @@ function umstellungen(d) {
|
||||
sich keine Aufgabe machen, ohne zu raten, welcher Satz gemeint
|
||||
war. */
|
||||
["eintraege", "einsatz", "TEXT"],
|
||||
|
||||
/* KATEGORIE-KANAELE (10.09.2026, Kapitel 7.2).
|
||||
|
||||
"Kategorie-Kanaele: z. B. Clipping-Team, Live-Moderation --
|
||||
Owner/rechte Hand haben Zugriff auf alle Kanaele."
|
||||
|
||||
Ein Kanal ist KEINE vierte Tabelle, sondern eine dritte Art Raum.
|
||||
Damit gilt fuer ihn ohne eine einzige neue Zeile alles, was schon
|
||||
da ist: Verlauf, Anhaenge, Suche, Ungelesen-Zaehler, der
|
||||
Live-Strom, das Wegraeumen. Eine eigene Tabelle haette all das
|
||||
ein zweites Mal gebraucht -- und die zweite Fassung waere die
|
||||
gewesen, in der die Zugriffsregel fehlt.
|
||||
|
||||
WELCHE Zustaendigkeit, steht in kategorie und kommt aus
|
||||
MODI_KATEGORIEN -- derselben Liste, aus der auch die Aufgaben
|
||||
ihre Kategorie nehmen. Ein Kanal "Clipping" und Aufgaben
|
||||
"clipping" waeren sonst zwei Woerter fuer dieselbe Sache. */
|
||||
["chat_raeume", "kategorie", "TEXT"],
|
||||
|
||||
/* ANGEHEFTETE NACHRICHTEN (10.09.2026, Kapitel 7.2).
|
||||
|
||||
"Ankuendigungs-Funktion: Pin-Nachrichten von Owner/rechte Hand
|
||||
an alle."
|
||||
|
||||
Am Anheften haengt ein ZWEITER Name (angeheftet_von) und nicht
|
||||
nur ein Datum. Eine Ankuendigung ohne Absender ist ein Aushang
|
||||
ohne Unterschrift -- und wer sie geloest hat, ist genau die
|
||||
Frage, die spaeter gestellt wird.
|
||||
|
||||
KEIN FREMDSCHLUESSEL AUF personen: Wird jemand geloescht, soll
|
||||
die Ankuendigung nicht mitverschwinden. Dieselbe Ueberlegung wie
|
||||
bei antwort_auf weiter oben. */
|
||||
["chat_nachrichten", "angeheftet_am", "TEXT"],
|
||||
["chat_nachrichten", "angeheftet_von", "INTEGER"],
|
||||
]) {
|
||||
try {
|
||||
const vorhanden = d.prepare(`PRAGMA table_info(${tabelle})`).all().map((s) => s.name);
|
||||
@@ -1695,6 +1774,36 @@ function umstellungen(d) {
|
||||
Vorschlag geworden ist. */
|
||||
checkListeErweitern(d, "eintraege", "status", "angenommen",
|
||||
["offen", "erledigt", "angenommen", "abgelehnt"], jetztStempel);
|
||||
|
||||
/* DIE DRITTE RAUMART (10.09.2026, Kapitel 7.2). Auf einer frischen
|
||||
Anlage steht 'kanal' schon im Bauplan -- dann findet der Aufruf die
|
||||
Regel bereits erweitert und tut nichts. */
|
||||
checkListeErweitern(d, "chat_raeume", "art", "kanal",
|
||||
["direkt", "gruppe", "kanal"], jetztStempel);
|
||||
|
||||
/* EIN KANAL JE ZUSTAENDIGKEIT, und die Datenbank haelt das fest.
|
||||
|
||||
Die Regel steht auch in der Route -- aber eine Regel, die nur in
|
||||
einer Route steht, gilt genau so lange, bis jemand eine zweite
|
||||
Route schreibt. Zwei Kanaele "Clipping" waeren zwei halbe
|
||||
Verlaeufe, und man merkt es erst, wenn jemand die Antwort im
|
||||
falschen sucht.
|
||||
|
||||
TEILINDEX (`WHERE art = 'kanal'`): Gruppen und Zweier-Gespraeche
|
||||
haben gar keine Kategorie; ohne die Bedingung waere schon die
|
||||
zweite Gruppe mit NULL ein Verstoss -- nein, NULL zaehlt in SQLite
|
||||
nicht als Dublette, aber der Index waere trotzdem groesser als
|
||||
noetig und wuerde etwas versprechen, was er nicht meint.
|
||||
|
||||
ER STEHT HIER UNTEN und nicht im Bauplan: Die Spalte `kategorie`
|
||||
entsteht auf einer bestehenden Anlage erst durch die Schleife
|
||||
weiter oben -- im Bauplan gaebe es sie beim Start noch nicht. */
|
||||
try {
|
||||
d.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_chat_kanal_kategorie
|
||||
ON chat_raeume (kategorie) WHERE art = 'kanal'`);
|
||||
} catch (fehler) {
|
||||
console.error("[workspace] Kanal-Index:", fehler?.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function db() {
|
||||
@@ -1902,11 +2011,17 @@ export function db() {
|
||||
CREATE TABLE IF NOT EXISTS chat_raeume (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
art TEXT NOT NULL DEFAULT 'direkt'
|
||||
CHECK (art IN ('direkt','gruppe')),
|
||||
/* Nur Gruppen haben einen Namen. Ein Zweier-Gespraech heisst
|
||||
immer nach dem Gegenueber -- und zwar fuer jeden anders. Ein
|
||||
gespeicherter Name waere fuer eine der beiden Seiten falsch. */
|
||||
CHECK (art IN ('direkt','gruppe','kanal')),
|
||||
/* Gruppen und Kanaele haben einen Namen. Ein Zweier-Gespraech
|
||||
heisst immer nach dem Gegenueber -- und zwar fuer jeden
|
||||
anders. Ein gespeicherter Name waere fuer eine der beiden
|
||||
Seiten falsch.
|
||||
Bei einem Kanal steht in kategorie, WORUM es geht; der Name
|
||||
wird daraus gebildet. Zwei Namen fuer dieselbe Zustaendigkeit
|
||||
sind der sichere Weg zu zwei Kanaelen mit je der Haelfte der
|
||||
Nachrichten darin. */
|
||||
name TEXT,
|
||||
kategorie TEXT,
|
||||
erstellt TEXT NOT NULL,
|
||||
erstellt_von INTEGER REFERENCES personen(id) ON DELETE SET NULL,
|
||||
/* Der Zeitpunkt der letzten Nachricht, mitgefuehrt. Ohne ihn
|
||||
@@ -4047,5 +4162,28 @@ export const sichtbareCreatorIds = (person) => ohneVerborgene(sichtbareCreatorId
|
||||
export const sichtbarePersonenIds = (person) => ohneVerborgene(sichtbarePersonenIdsRoh(person), person);
|
||||
export const betreuerIds = (person) => ohneVerborgene(betreuerIdsRoh(person), person);
|
||||
export const einladbareIds = (person) => ohneVerborgene(einladbareIdsRoh(person), person);
|
||||
export const schreibbareIds = (person) => ohneVerborgene(schreibbareIdsRoh(person), person);
|
||||
/* SICH SELBST NICHT -- UND ZWAR AUCH DANN NICHT, WENN DIE LISTE
|
||||
AUSGESCHRIEBEN WIRD (10.09.2026).
|
||||
|
||||
schreibbareIdsRoh() endet mit `basis.delete(person.id)`. Das greift
|
||||
aber nur, solange eine Liste gebaut wird. Wer `siehtAlles` hat,
|
||||
bekam `null` = jeder, und die Route setzte dafuer `id <> ?` ein --
|
||||
auch richtig.
|
||||
|
||||
Dazwischen liegt der Fall, den beide nicht abdecken: Sobald es
|
||||
jemanden zu VERBERGEN gibt, schreibt ohneVerborgene() aus `null`
|
||||
eine echte Liste -- und die enthaelt alle ausser den Verborgenen,
|
||||
also auch die fragende Person selbst. Spicy Media stand dadurch in
|
||||
der eigenen Auswahl, und darfSchreibenMit() haette ein Gespraech mit
|
||||
sich selbst durchgelassen.
|
||||
|
||||
Aufgefallen ist das nicht beim Lesen des Codes, sondern an einer
|
||||
Zeile Messausgabe: "Spicy Media hat es NICHT (VanVan, Filipe,
|
||||
Cigdem)" -- der erste Name war ihr eigener. Der Fall entsteht erst,
|
||||
seit es ueberhaupt jemanden zu verbergen gibt; vorher konnte es ihn
|
||||
nicht geben. */
|
||||
export const schreibbareIds = (person) => {
|
||||
const ids = ohneVerborgene(schreibbareIdsRoh(person), person);
|
||||
return ids === null ? null : ids.filter((i) => i !== person?.id);
|
||||
};
|
||||
export const pipelineIds = (person) => ohneVerborgene(pipelineIdsRoh(person), person);
|
||||
|
||||
Reference in New Issue
Block a user