/* ===================================================================== Die kurze Erklärung auf jeder Seite (14.09.2026) Filipe: *„ich will in jeder seite auch eine kleine detaillierte aber schnelle erklärung kurz und knapps noch zu jeder seite."* --------------------------------------------------------------------- DREI ZEILEN, UND DIE DRITTE RECHNET SICH SELBST AUS WOFÜR wozu es diese Seite gibt DU TUST was man hier konkret macht — nicht was man könnte SIEHT wer diese Seite überhaupt öffnen kann Die ersten beiden sind geschriebener Text. Die dritte ist es NICHT: Sie wird aus der Rechtetafel gerechnet, aus derselben, aus der auch die Schranke ihre Entscheidung holt. Das ist der ganze Grund, warum diese Zeile hier stehen darf. Hätte ich sie danebengeschrieben, wäre sie beim ersten Umstellen in „Wer sieht was" falsch geworden — und zwar STILL falsch, in einem Satz, der behauptet, wer etwas sieht. Genau diese Sorte Zweitschrift hat im Projekt schon dreimal Daten oder Vertrauen gekostet (die abgeschriebene Spaltenliste, die zweite Rollenreihenfolge im Browser, das fest eingetragene Wort „Agentur"). Eine Angabe über Sichtbarkeit ist der schlechteste Ort, um es ein viertes Mal zu tun. --------------------------------------------------------------------- WARUM DIE ERKLÄRUNG HINTER DERSELBEN SCHRANKE STEHT WIE DIE SEITE Wer eine Seite nicht öffnen darf, bekommt auch ihre Erklärung nicht — mit 404, nicht mit 403. Sonst wäre dieser Weg eine Landkarte des Hauses: Ein Modi könnte abfragen, dass es „Personen & Zugänge" gibt, wozu die Seite dient und wer sie sieht. Die Seite selbst antwortet ihm mit „gibt es nicht"; die Erklärung dazu muss dasselbe sagen. --------------------------------------------------------------------- WAS HIER BEWUSST NICHT STEHT Keine Anleitung in Schritten („klicke oben rechts auf …"). Solche Texte altern mit jedem Umbau der Oberfläche und werden dann zu Anweisungen, die ins Leere zeigen. Was hier steht, ist der ZWECK — der ändert sich, wenn die Seite sich ändert, und nicht vorher. ===================================================================== */ import express from "express"; import { sitzungLesen, ROLLEN_NAME, ROLLEN_REIHE } from "./workspace.js"; import { darfSeite, tafelJetzt } from "./rechte.js"; export const erklaerungRouter = express.Router(); /** Der Text zu jeder Seite. * * `wofuer` beantwortet „wozu gibt es das?", `tun` beantwortet „was * mache ich hier?". `merke` ist der eine Satz, der am häufigsten * missverstanden wird — er steht nur dort, wo es ihn wirklich gibt. * Ein Feld, das auf jeder Seite gefüllt ist, wird nicht mehr gelesen. */ export const ERKLAERUNGEN = { "start.html": { wofuer: "Deine Startseite. Jede Kachel führt zu einem Bereich, und du siehst nur die, die dir offenstehen.", tun: "Aussuchen, wo du hinwillst – oder oben auf die Glocke schauen, wenn etwas auf dich wartet.", }, "uebersicht.html": { wofuer: "Alle betreuten Creator nebeneinander, mit Zahlen, offenen Aufgaben und dem Datum des letzten Kontakts.", tun: "Die oberste Karte zuerst ansehen – sortiert wird nach dem, was Handlung verlangt, nicht nach Namen.", }, "aufgaben.html": { wofuer: "Alle Aufgaben des Teams in vier Spalten, mit Frist, Zuständigkeit und Rückmeldungen.", tun: "Eine Aufgabe weiterschieben, sobald sich ihr Stand ändert – nicht sammeln und später nachtragen.", merke: "Was hier nicht steht, passiert nicht. Aufgaben aus einem Call landen automatisch hier.", }, "kalender.html": { wofuer: "Alle Termine des Teams an einer Stelle, mit Serien und Feiertagen.", tun: "Termin anlegen und unten auswählen, wer ihn im eigenen Kalender sehen soll.", merke: "Wen du nicht auswählst, sieht den Termin nicht – auch nicht, dass es ihn gibt.", }, "chat.html": { wofuer: "Nachrichten an einzelne Menschen oder an einen Kanal.", tun: "Schreiben. Auf eine Nachricht kannst du auch nur reagieren, statt zu antworten.", }, "calls.html": { wofuer: "Alle Gespräche mit Datum, Teilnehmern und Protokoll.", tun: "Nach dem Call das Protokoll schreiben; die To-dos daraus landen direkt im Aufgabenbrett.", merke: "Ein Call ohne Protokoll bleibt auf der Startseite stehen, bis eines da ist.", }, "dateien.html": { wofuer: "Alle Dateien zu einem Creator, jede mit Stand und Versionsverlauf.", tun: "Hochladen und den Stand setzen – die aktuelle Fassung ist dadurch immer erkennbar.", }, "content.html": { wofuer: "Alle Videoideen mit Säule, Hook, Format und geplantem Datum.", tun: "Eine Idee eintragen und ihr eine Säule geben; die Strecke danach ist immer dieselbe.", }, "bereich.html": { wofuer: "Die Einträge dieses Bretts, je nach Brett mit Art, Datum, Dringlichkeit oder Bewertung.", tun: "Eintrag anlegen, Art wählen, und bei „Gilt für“ entscheiden, ob er dir gehört oder allen.", }, "wissen.html": { wofuer: "Anleitungen, PDFs und Kurzfassungen – durchsuchbar, nach Themen sortiert.", tun: "Nachschlagen, bevor du fragst – und selbst etwas dazulegen, wenn du es erklärt hast.", }, "report.html": { wofuer: "Je Creator und Zeitraum: was lief, was nicht, was als Nächstes kommt.", tun: "Zeitraum wählen und den Rückblick schreiben, solange die Zahlen dazu danebenstehen.", }, "leistung.html": { wofuer: "Livestunden, Zuschauer und Einnahmen je Tag – aus Backstage oder von Hand.", tun: "Zeitraum importieren oder einen Tag von Hand eintragen, dann mit den Wochenzielen vergleichen.", }, "scouting.html": { wofuer: "Alle Kontakte mit Stufe, Kanal, Reichweite und dem, was zuletzt passiert ist.", tun: "Jeden Kontakt eine Stufe weiterschieben – oder bewusst beenden, statt ihn liegen zu lassen.", }, "startcheck.html": { wofuer: "Ein Fragebogen je neuem Creator und die Arbeitspakete, die daraus entstehen.", tun: "Gemeinsam mit ihr ausfüllen, nicht über sie – die Antworten sind der Anfang eurer Zusammenarbeit.", }, "profil.html": { wofuer: "Zahlen, Aufgaben, Dateien, Termine und Verlauf zu einer Creatorin an einer Stelle.", tun: "Nachsehen, bevor du mit ihr oder ihm sprichst.", }, "steckbrief.html": { wofuer: "Dein Bild, ein paar Worte über dich und deine Kanäle.", tun: "Ausfüllen. Dein Bild steht danach überall dort, wo dein Name auftaucht.", merke: "Das hier gehört dir. Niemand außer dir ändert daran etwas.", }, "personen.html": { wofuer: "Zugänge: wer hereinkommt, mit welcher Rolle, und wer wen betreut.", tun: "Zugang anlegen und den Code persönlich weitergeben.", merke: "Ein Zugangscode wird genau einmal angezeigt. Danach gibt es nur einen neuen, keinen alten.", }, "automation.html": { wofuer: "Jede Automatik des Hauses mit Zustand, letzter Ausführung und Schalter.", tun: "Nachsehen, was gerade greift, bevor du dich über etwas wunderst, das du nicht angestoßen hast.", }, "team.html": { wofuer: "Die Arbeitslast von Managern und Scouts nebeneinander.", tun: "Stellen suchen, an denen jemand zu viel hat – und dort umverteilen.", merke: "Das ist keine Kontrolle. Die Seite gibt es, um fehlende Unterstützung zu finden.", }, "teamlage.html": { wofuer: "Was in deinem Team gerade offen ist – und bei wem.", tun: "Die offenen Punkte durchgehen und ansprechen.", merke: "„Offen“ heißt: darüber habt ihr noch nicht geredet. Es ist keine Note über jemanden.", }, "entwicklung.html": { wofuer: "34 Beobachtungen je Person, gesetzt von zwei Augenpaaren unabhängig voneinander.", tun: "Pro Person die Punkte setzen, die du wirklich beobachtet hast. Den Rest lässt du offen.", merke: "Keine Noten, keine Punktzahl. Das Ergebnis ist die Abweichung zwischen euch beiden – ein Gesprächsanlass.", }, "talente.html": { wofuer: "Kandidaten für dein Mod-Team in fünf Stufen – mit 21 Merkmalen und 6 Warnzeichen je Karte.", tun: "Pro Karte den einen nächsten Schritt machen, der dort steht.", merke: "Hier stehen Namen von Menschen, die nichts davon wissen. Nichts davon geht nach außen.", }, "rechte.html": { wofuer: "Jede Rolle mal jede Seite als Tafel – und jedes Feld lässt sich umstellen.", tun: "Ein Feld umstellen und speichern. Die Änderung gilt sofort, auch für alle, die gerade angemeldet sind.", merke: "Drei Felder lassen sich nicht abschalten – sonst könntest du dich selbst aussperren.", }, "treff-regeln.html": { wofuer: "Die Regeln des Treffs und was passiert, wenn sie jemand nicht einhält.", tun: "Einmal lesen. Danach weißt du, was hier gilt und an wen du dich wendest, wenn etwas schiefläuft.", }, "treff-moderation.html": { wofuer: "Offene Meldungen, erledigte Fälle und jede Maßnahme mit Begründung.", tun: "Eine Meldung ansehen und entscheiden: Hinweis, Pause oder Ausschluss – immer mit Begründung.", merke: "Jede Maßnahme steht nachher mit Grund da. Das schützt auch dich.", }, }; /** Die Rollen, die diese Seite gerade öffnen dürfen — als Namen. * * AUS DER TAFEL GERECHNET, NICHT DANEBENGESCHRIEBEN. Wer in „Wer * sieht was" ein Feld umstellt, ändert damit auch diesen Satz; eine * zweite Liste hier wäre spätestens dann eine Behauptung. * * In der Reihenfolge des Hauses (ROLLEN_REIHE), damit die rechte Hand * auch hier direkt hinter DogFather steht. */ function sichtbarFuer(pfad) { const erlaubt = tafelJetzt()[pfad] || []; const bekannt = ROLLEN_REIHE.filter((r) => erlaubt.includes(r)); /* Rollen, die (noch) nicht in der Rangfolge stehen -- heute nur 'gast'. Sie faellt ans Ende, statt stillschweigend zu fehlen. */ const rest = erlaubt.filter((r) => !bekannt.includes(r)); return [...bekannt, ...rest].map((r) => ROLLEN_NAME[r] || r); } erklaerungRouter.get("/workspace/api/erklaerung", (req, res) => { try { const person = sitzungLesen(req); if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" }); /* Nur der Dateiname, und nur aus dem eigenen Verzeichnis. Ohne das waere `?seite=../../etwas` eine Frage, die dieser Weg gar nicht beantworten koennen soll. */ const datei = String(req.query?.seite || "").trim(); if (!/^[a-z0-9-]+\.html$/.test(datei)) { return res.status(400).json({ fehler: "ungueltig" }); } const pfad = "/workspace/" + datei; /* DIESELBE SCHRANKE WIE FUER DIE SEITE SELBST -- und dieselbe Antwort, wenn sie zu ist: 404, nicht 403. Wer "kein Zugriff" liest, weiss, dass es die Seite gibt. */ if (!darfSeite(person, pfad)) { return res.status(404).json({ fehler: "nicht_gefunden" }); } const text = ERKLAERUNGEN[datei]; /* Eine Seite ohne Text ist KEIN Fehler, aber auch keine leere Erklaerung: Der Browser bekommt `hat_text: false` und zeichnet gar nichts. Ein Kasten mit drei leeren Zeilen waere schlechter als keiner. */ if (!text) return res.json({ hat_text: false, seite: datei }); res.json({ hat_text: true, seite: datei, wofuer: text.wofuer, tun: text.tun, merke: text.merke || null, sichtbar: sichtbarFuer(pfad), }); } catch (fehler) { console.error("[erklaerung]", fehler?.message); res.status(503).json({ fehler: "nicht_verfuegbar" }); } });