Files
dogfather-universe/server/workspace-erklaerung.js
T
DogFatherGitandClaude Opus 5 de19684883 Jede Seite erklaert sich selbst -- drei Zeilen, und die dritte rechnet sich aus
Filipe: "ich will in jeder seite auch eine kleine detaillierte aber
schnelle erklaerung kurz und knapps noch zu jeder seite."

DREI ZEILEN AUF JEDER DER 25 SEITEN

  WOFUER    was auf dieser Seite steht
  DU TUST   was man hier konkret macht
  MERKE     nur dort, wo es etwas gibt, das man falsch versteht
  SIEHT     wer diese Seite ueberhaupt oeffnen kann

Offen sichtbar und nicht hinter einem Aufklapper: "schnell" war seine
Bedingung, und eine Erklaerung, die man erst aufklappen muss, ist
genau dann nicht da, wenn man sie braucht.

DIE ZEILE "SIEHT" IST GERECHNET, NICHT GESCHRIEBEN

Sie kommt aus der Rechtetafel -- aus derselben, aus der auch die
Schranke ihre Entscheidung holt. Haette ich sie danebengeschrieben,
waere sie beim ersten Umstellen in "Wer sieht was" still falsch
geworden, in einem Satz, der behauptet, wer etwas sieht. Die Pruefung
stellt die Tafel deshalb wirklich um und sieht nach, ob der Satz
mitwandert.

Die Erklaerung haengt hinter derselben Schranke wie die Seite: Wer sie
nicht oeffnen darf, bekommt 404 -- nicht 403, sonst waere dieser Weg
eine Landkarte des Hauses.

Eingebunden ist sie nicht ueber eine Liste von Dateinamen, sondern
ueber "wer kopf.js laedt": die 26. Seite ist damit automatisch dabei.
Fuenf Seiten haben keine Kopfzeile (Chat, Kalender, Start, Team,
Teamlage) -- dort haengt der Kasten an <main>, sonst waeren
ausgerechnet die haeufigsten Seiten lautlos leer geblieben.

ZWEI EIGENE MAENGEL, GEFUNDEN DURCH HINSEHEN, NICHT DURCH DIE PRUEFUNG

Die erste Fassung war gruen -- und stand mitten auf dem Buehnenbild,
mit einem Lichtfleck hinter "SIEHT". Und "Wofuer" sprach fast woertlich
die Unterzeile darueber nach. Beides misst die Pruefung jetzt: Kontrast
mit dem dritten Ausgang ("steht auf der Buehne" ist kein Ergebnis) und
die Wortueberschneidung mit der Unterzeile.

ZWEI BEFUNDE, DIE SCHON VORHER ROT WAREN

1. pruef-tempo-workspace verglich die UNKOMPRIMIERTE Groesse gegen eine
   Grenze, die fuer die Leitung gedacht war -- waehrend der Kommentar
   daneben "das ist, was wirklich ueber die Leitung ging" behauptete.
   Live steht Caddy davor. Nachgemessen an der echten Adresse:
   start.css 345 KB -> 111 KB zstd. Die Pruefung wog also 1,2 MB, wo
   Filipe 500 KB bekommt. Jetzt wird Komprimierbares im Pruefstand
   gzip-gepackt (115 KB gegen 111 KB live -- auf vier Prozent genau),
   geurteilt wird ueber die uebertragene Groesse, berichtet werden
   beide. Dabei prompt in die dokumentierte Falle getappt: `body()` auf
   einen Ereignisstrom endet nie, der Lauf blieb stehen. Jetzt
   ausgenommen, plus Notbremse je Koerper.

2. pruef-ueberlappung konnte seit Tagen nicht mehr zeigen, dass sie
   eine Ueberlappung ueberhaupt findet: Ihre Gegenprobe legt einen Knopf
   auf einen anderen, traf aber 6 px daneben. Zwei Ursachen in einer
   Zeile -- `position: fixed` bezieht sich auf einen Vorfahren mit
   `transform`, und die `transition` der Knoepfe liefert beim sofortigen
   Messen die Lage von vorher. Jetzt `transform` + `transition: none`:
   34x36 px erkannt, 20 Seiten-Breiten-Paare, 0 Befunde.

PRUEFUNGEN: pruef-erklaerung neu mit 44, davon 8 im Browser (Seite mit
und ohne Kopfzeile, Handy, Kontrast, Echo). Mein Schild stand auf
10,88 px und lag unter der Hausgrenze von 11,5 -- gefunden von
pruef-css-klassen, nicht vom Auge.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-14 18:20:30 +02:00

230 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =====================================================================
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: "Den Zeitraum durchgehen und drei Dinge festhalten: was lief, was nicht, was als Nächstes kommt.",
},
"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: "Die Fragen durchgehen; daraus entstehen automatisch die ersten Arbeitspakete.",
},
"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. Was hier steht, gilt auch – und du siehst, was passiert, wenn es jemand nicht einhält.",
},
"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" });
}
});