Deutsch oeffnet mit „ und schliesst mit “. An 199 Stellen, die ein
Mensch liest, stand als Schlusszeichen ein GERADES " -- „Nächstes"
statt „Nächstes“. Auf sechzehn oeffentlichen Seiten, in vierzig
Dateien des Workspace und in zwoelf Servermodulen, die Texte
verschicken.
Auf dem Bildschirm sieht man den Unterschied sofort. Beim Schreiben
nicht: Das gerade " liegt auf der Tastatur, die anderen nicht.
WARUM EIN ERSTER ANLAUF ZURUECKGENOMMEN WURDE
Ein gerades " ist an vielen Stellen SYNTAX und kein Schriftzeichen --
Grenze einer Zeichenkette, Grenze eines HTML-Attributs, Zeichen in
einem regulaeren Ausdruck. Wer stumpf ersetzt, macht aus
„<a href="https://… ein kaputtes Attribut
/^["'„»\s]+|["'“«.\s]+$/ einen kaputten Ausdruck
"… nichts „mal " + "eben …" eine kaputte Zeichenkette
DIE UNTERSCHEIDUNG LAEUFT AN MERKMALEN, NICHT AN EINER LISTE
< > = dazwischen -> HTML-Marke oder Attribut
endet auf Leerzeichen -> die Zeichenkette hoert hier auf, der
Satz geht in der naechsten Zeile weiter.
Ein deutsches Schlusszeichen steht NIE
hinter einem Leerzeichen.
Rueckstrich mittendrin -> regulaerer Ausdruck
${ ohne } -> mitten in einem Ausdruck
Eine Liste erlaubter Ausnahmen waere die naechste, die niemand
pflegt. Zwoelf Stellen bleiben dadurch stehen, alle zwoelf einzeln
angesehen und alle zwoelf zu Recht -- dort steht das richtige
Schlusszeichen ohnehin weiter unten im Satz.
Fuenf davon waren allerdings ECHTE Fehler HINTER dem Link
(`…>HasiDog</a>".`) -- die erste Regel hatte nur das Attribut
gesehen, nicht den Satz danach. Gezielt nachgezogen.
Ein maskiertes `\"` in workspace-vorlagen.js (28 Hooks) wird zu “ --
ohne Rueckstrich, denn “ begrenzt nichts.
KOMMENTARE BLEIBEN, WIE SIE SIND. Dort liest es niemand ausser mir;
eine Wache, die auch Kosmetik anmahnt, wird weggeklickt. Beim ersten
Messen fielen ausserdem acht Stellen aus buehne.html faelschlich an,
weil `/* */` in HTML (in <style> und <script>) nicht ausgeblendet
war -- jetzt schon.
DIE WACHE DAZU
pruef-struktur prueft es ab sofort mit derselben Regel: 322 Dateien
mit sichtbarem Text, 0 Funde, und die zwoelf bewussten Ausnahmen
werden GEZAEHLT und genannt (erlaubt: 12). Eine Ausnahme, die niemand
sieht, waechst -- und irgendwann steht der echte Fall darin.
GEPRUEFT
pruef-struktur 59 -> 68 Pruefungen, 0 Fehler
node --check auf allen geaenderten JS-Dateien
nachgemessen: 199 geaendert, 12 mit Grund stehen geblieben
gruen geblieben: bewerbung-aufgaben 163, nachwuchs 262,
reaktion 421, support 63, content 45, terminregel 35, treff 85
Und nachgesehen, ob eine Pruefung noch die alte Schreibweise
ERWARTET: 13 Fundstellen, alle dreizehn nur Text in ihrer eigenen
Ausgabe, keine einzige ein Vergleich mit dem Seitentext.
GEGENPROBE: In reaktion.html ein Schlusszeichen zurueckgedreht ->
„workspace/reaktion.html:546 „Nächstes"", mit Datei und Zeile. Und
die Erkennung einzeln gegen HTML-Attribut, fortgesetzte
Zeichenkette, regulaeren Ausdruck und eingesetzten Wert geprueft.
Stempel gesetzt: workspace 670 Verweise in 45 Dateien, oeffentlich
554 in 48 Seiten.
Co-Authored-By: Claude Opus 5 <[email protected]>
294 lines
13 KiB
JavaScript
294 lines
13 KiB
JavaScript
/* =====================================================================
|
||
WILLKOMMEN — die Seite, die erklärt, was es hier gibt
|
||
=====================================================================
|
||
|
||
Auftrag vom 21.09.2026, Abschnitte 7 und 8:
|
||
|
||
*"Beim Öffnen soll die Person persönlich begrüßt werden ...
|
||
Willkommen, [Name]! – danach folgt eine verständliche Einführung
|
||
in die Website ... Die Willkommensseite darf nicht für alle Nutzer
|
||
identisch sein. Sie muss vollständig rollenbasiert und individuell
|
||
aufgebaut werden ... Die Erklärung soll sich automatisch an die
|
||
tatsächlich vorhandenen Rechte des jeweiligen Nutzers anpassen."*
|
||
|
||
DAS LETZTE IST DER GANZE BAU. Eine Erklärung, die aufzählt, was es
|
||
gibt, ist am Tag danach falsch — jemand baut eine Kachel dazu, und
|
||
die Seite verschweigt sie. Deshalb erklärt sie GENAU DIE KACHELN,
|
||
die diese Person wirklich hat: dieselbe Liste, aus der die
|
||
Startseite gebaut wird (`bereicheFuer`), durch denselben Filter
|
||
(`darfSeite`). Wer morgen eine Kachel bekommt, bekommt automatisch
|
||
auch ihre Erklärung.
|
||
|
||
DREI EBENEN, und jede hat einen Grund:
|
||
|
||
Der Untertitel steht schon an der Kachel. Er ist die kürzeste
|
||
wahre Auskunft und wird deshalb nicht neu
|
||
erfunden.
|
||
Der lange Satz nur bei den Seiten, wo eine Zeile zu wenig ist.
|
||
NACH ZIEL abgelegt, nicht nach Name: Namen
|
||
ändern sich (am 21.09. gleich zweimal), das
|
||
Ziel bleibt.
|
||
Die Einleitung je Rolle ein eigener Absatz. Das ist die eine
|
||
Stelle, an der wirklich nach Rolle unterschieden
|
||
wird — und es sind vier Sätze, keine vier
|
||
Seitenlisten.
|
||
|
||
WAS NICHT ERKLÄRT WIRD, STEHT AUCH NICHT DA. Der Auftrag lässt die
|
||
Wahl zwischen "gar nicht erklären" und "deutlich als nicht relevant
|
||
behandeln". Gar nicht ist richtig: Eine Liste von Dingen, die man
|
||
nicht darf, ist keine Orientierung, sondern eine Kränkung.
|
||
===================================================================== */
|
||
|
||
import express from "express";
|
||
import {
|
||
sitzungLesen, bereicheFuer, zusatzBereicheFuer, rollentextFuer,
|
||
ROLLEN_NAME, istDogFather, darfAufgabenVerteilen,
|
||
} from "./workspace.js";
|
||
import { darfSeite } from "./rechte.js";
|
||
|
||
export const willkommenRouter = express.Router();
|
||
|
||
/* ---------------------------------------------------------------------
|
||
DIE EINLEITUNG JE ROLLE
|
||
---------------------------------------------------------------------
|
||
|
||
Sie sagt in zwei, drei Sätzen, WOZU jemand hier ist. Die Kacheln
|
||
darunter sagen, WOMIT. Beides zu mischen ergäbe einen Text, den
|
||
niemand liest.
|
||
|
||
Der Ton ist bewusst derselbe wie im ganzen Haus: auf Augenhöhe,
|
||
ohne jemanden über jemanden zu stellen. */
|
||
const EINLEITUNG = {
|
||
admin: {
|
||
was: "Das hier ist deine Seite — von hier aus läuft alles.",
|
||
dazu: "Du siehst jede Kachel, die es gibt, und entscheidest am Ende, "
|
||
+ "was passiert. Wenn dir etwas Vorgeschlagenes nicht passt, lehnst "
|
||
+ "du es ab; nichts geschieht ohne dich.",
|
||
},
|
||
hand: {
|
||
was: "Du vertrittst DogFather im Alltag und hältst das Team zusammen.",
|
||
dazu: "Du siehst dieselben Aufgaben, Ideen und Auswertungen wie er, "
|
||
+ "verteilst Aufgaben an die Modis und legst neue Leute an — samt "
|
||
+ "ihrem Zugangscode.",
|
||
},
|
||
linke: {
|
||
was: "Du führst das Team durch den Tag — mit denselben Werkzeugen wie "
|
||
+ "die rechte Hand.",
|
||
/* NUR, WAS DA IST (22.09.2026).
|
||
----------------------------------------------------------------
|
||
Hier stand: "Was du NICHT hast: Personen anlegen, Rollen ändern
|
||
und den vertraulichen Meldeweg."
|
||
|
||
Filipe: "keiner soll wissen was er nicht sieht das geht keinen
|
||
was an. sie sehen die sachen ja nicht weil es sie nichts angeht
|
||
und das muss auch nicht erwaehnt werden."
|
||
|
||
Er hat recht, und es passt zur Hausregel, die ueberall sonst
|
||
schon gilt: Der Server antwortet mit 404 statt 403, damit ein
|
||
"das darfst du nicht" nicht verraet, dass es etwas gibt. Ein
|
||
Satz, der dasselbe aufzaehlt, hebt diese Sorgfalt wieder auf. */
|
||
dazu: "Aufgaben verteilen, Stände verfolgen, Rückmeldung geben — und "
|
||
+ "dabei sehen, wie es bei jedem gerade läuft. Alles dafür findest "
|
||
+ "du unten.",
|
||
},
|
||
modi: {
|
||
was: "Schön, dass du da bist. Hier findest du alles für deinen Tag.",
|
||
dazu: "Unter „Aufgaben“ steht, was gerade für dich dran ist — du "
|
||
+ "nimmst an, was du schaffst, und sagst ab, was nicht geht. Eine "
|
||
+ "Absage ist völlig in Ordnung, schreib einfach kurz warum.",
|
||
},
|
||
gast: {
|
||
was: "Schön, dass du da bist.",
|
||
dazu: "Hier triffst du die anderen aus der Community. Schau dich um, "
|
||
+ "schreib etwas, oder lies erst mal mit — beides ist richtig.",
|
||
},
|
||
};
|
||
|
||
/* ---------------------------------------------------------------------
|
||
DIE LANGEN SÄTZE — nach ZIEL, nicht nach Name
|
||
---------------------------------------------------------------------
|
||
|
||
Nur dort, wo eine Zeile zu wenig ist. Wer zu jeder Kachel einen
|
||
Absatz schreibt, schreibt eine Seite, die niemand zu Ende liest.
|
||
|
||
Der Schlüssel ist der Dateiname ohne Parameter. Am 21.09. sind in
|
||
diesem Haus zwei Kachelnamen gewandert; ein Text, der am Namen
|
||
hängt, wäre dabei stumm geworden, ohne dass es jemand merkt. */
|
||
const LANG = {
|
||
"aufgaben.html":
|
||
"Oben steht, wie es bei dir gerade aussieht — was offen ist, was läuft, "
|
||
+ "was erledigt ist. Darunter die Karten. Bekommst du eine Aufgabe, "
|
||
+ "kannst du sie annehmen oder ablehnen; wenn du ablehnst, schreib "
|
||
+ "kurz warum, dann muss niemand nachfragen.",
|
||
"chat.html":
|
||
"Einzelgespräche und Gruppen. Mit @ und dem Namen sprichst du jemanden "
|
||
+ "direkt an — er bekommt dann eine eigene Meldung, auch wenn er den "
|
||
+ "Chat gerade nicht offen hat.",
|
||
"kalender.html":
|
||
"Alles mit Datum an einer Stelle. Termine über mehrere Tage sind "
|
||
+ "durchgehend markiert, am Handy tippst du einen Tag an und siehst, "
|
||
+ "was dort ansteht.",
|
||
"bereich.html":
|
||
"Ein Brett zum Sammeln. Schreib hinein, was dir einfällt — es geht "
|
||
+ "nichts verloren, und niemand muss es sofort beantworten.",
|
||
"befinden.html":
|
||
"Ein paar Fragen über dich, nur für dich. Niemand aus dem Team sieht "
|
||
+ "deine Antworten — am Ende bekommst du eine kurze Zusammenfassung.",
|
||
"steckbrief.html":
|
||
"Deine Seite: Foto, ein paar Worte über dich. Du entscheidest, was "
|
||
+ "darauf steht.",
|
||
"hilfe.html":
|
||
"Wenn etwas vorgefallen ist, das niemand mitlesen soll. Nur DogFather "
|
||
+ "und die rechte Hand sehen diese Meldungen.",
|
||
"notizen.html":
|
||
"Dein Block. Schreib hinein, was dir einfällt — es speichert sich von "
|
||
+ "selbst, und niemand außer dir sieht es. Auch DogFather nicht.",
|
||
"support.html":
|
||
"Etwas an der Seite funktioniert nicht oder sieht falsch aus? Schreib "
|
||
+ "einen Satz dazu und häng ein Bildschirmfoto an — DogFather und die "
|
||
+ "rechte Hand bekommen es sofort und melden sich, wenn es erledigt ist.",
|
||
"treff-regeln.html":
|
||
"Wie wir hier miteinander umgehen. Kurz, und es lohnt sich einmal.",
|
||
"unsere-seiten.html":
|
||
"Ob gerade gestreamt wird, wann es losgeht, und wo du die Kanäle "
|
||
+ "findest.",
|
||
"personen.html":
|
||
"Zugänge anlegen, sperren und Rollen ändern. Der Code wird genau "
|
||
+ "einmal angezeigt — danach nie wieder.",
|
||
"teamlage.html":
|
||
"Wer gerade wie unterwegs ist. Gedacht zum Hinsehen, nicht zum "
|
||
+ "Vergleichen.",
|
||
"werdegang.html":
|
||
"Wie sich jemand über die Zeit entwickelt hat. Keine Note, kein "
|
||
+ "Ranking — nur die Bewegung.",
|
||
"willkommen.html": null, /* sich selbst erklärt sie nicht */
|
||
};
|
||
|
||
/* ---------------------------------------------------------------------
|
||
DIE ROUTE
|
||
--------------------------------------------------------------------- */
|
||
willkommenRouter.get("/workspace/api/willkommen", (req, res) => {
|
||
try {
|
||
const person = sitzungLesen(req);
|
||
if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" });
|
||
|
||
/* DIESELBE LISTE WIE DIE STARTSEITE, durch denselben Filter. Eine
|
||
eigene Aufzählung wäre die, die beim nächsten Umbau veraltet. */
|
||
const roh = [
|
||
...(bereicheFuer(person) || []),
|
||
...(zusatzBereicheFuer(person) || []),
|
||
];
|
||
const offen = roh.filter((k) => {
|
||
if (k?.aussen) return true;
|
||
const ziel = String(k?.ziel || "");
|
||
if (!ziel) return false;
|
||
return darfSeite(person, "/workspace/" + ziel.split("?")[0]);
|
||
});
|
||
|
||
/* NACH GRUPPEN, in der Reihenfolge, in der sie auf der Startseite
|
||
stehen -- wer dort von oben liest, findet hier dasselbe wieder. */
|
||
const gruppen = [];
|
||
for (const k of offen) {
|
||
const name = k.gruppe || "Sonst";
|
||
let g = gruppen.find((x) => x.name === name);
|
||
if (!g) { g = { name, kacheln: [] }; gruppen.push(g); }
|
||
const datei = String(k.ziel || "").split("?")[0];
|
||
g.kacheln.push({
|
||
name: k.name,
|
||
unter: k.unter || "",
|
||
ziel: k.ziel,
|
||
zeichen: k.zeichen || null,
|
||
/* DERSELBE FARBTON WIE AUF DER STARTSEITE (22.09.2026).
|
||
Filipe: "die kacheln in der willkommens seite sollen die
|
||
selben farben haben wie in der startseite."
|
||
|
||
Durchgereicht, nicht neu vergeben: `[data-ton="N"]` in
|
||
start.css setzt `--ton`, und start.css laedt diese Seite
|
||
ohnehin. Eine eigene Farbtabelle waere die, die beim
|
||
naechsten Farbwechsel auseinanderlaeuft -- und genau das ist
|
||
am 19.09. schon einmal passiert, als elf Seiten die neue
|
||
Kachelfarbe nie bekamen. */
|
||
ton: Number.isFinite(k.ton) ? k.ton : null,
|
||
gross: !!k.gross,
|
||
/* Der lange Satz nur, wenn es einen gibt -- `null` heißt
|
||
ausdrücklich "hier reicht die Zeile". */
|
||
lang: Object.prototype.hasOwnProperty.call(LANG, datei) ? LANG[datei] : null,
|
||
});
|
||
}
|
||
|
||
const ein = EINLEITUNG[person.rolle] || {
|
||
was: "Schön, dass du da bist.",
|
||
dazu: "Hier findest du alles, was für dich freigeschaltet ist.",
|
||
};
|
||
|
||
res.json({
|
||
name: person.name,
|
||
rolle: person.rolle,
|
||
rollenname: ROLLEN_NAME[person.rolle] || null,
|
||
rollentext: rollentextFuer(person),
|
||
gruss: `Willkommen, ${person.name}!`,
|
||
einleitung: ein,
|
||
gruppen,
|
||
/* Ein paar Sätze, die nur für bestimmte Rollen gelten und zu
|
||
keiner einzelnen Kachel gehören. */
|
||
hinweise: hinweiseFuer(person),
|
||
/* Damit die Seite die Zahl nennen kann, ohne sie zu zählen. */
|
||
kachelzahl: offen.length,
|
||
});
|
||
} catch (f) {
|
||
console.error("[willkommen]:", f?.message);
|
||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||
}
|
||
});
|
||
|
||
/** Sätze, die zu keiner Kachel gehören, aber zur Rolle. */
|
||
function hinweiseFuer(person) {
|
||
const aus = [];
|
||
if (darfAufgabenVerteilen(person)) {
|
||
aus.push({
|
||
titel: "Aufgaben verteilen",
|
||
text: "Beim Anlegen einer Aufgabe kannst du eine Person aussuchen — "
|
||
+ "oder mehrere. Bei mehreren fragst du noch, ob jede ihren Teil "
|
||
+ "macht oder ob die Erste, die Zeit hat, übernimmt. Ist jemand "
|
||
+ "fertig, kannst du kurz Rückmeldung geben.",
|
||
});
|
||
}
|
||
if (person.rolle === "modi") {
|
||
aus.push({
|
||
titel: "Du musst nicht alles annehmen",
|
||
text: "Wenn eine Aufgabe gerade nicht geht, lehn sie ab und schreib "
|
||
+ "einen Satz dazu. Das ist ausdrücklich vorgesehen und niemandem "
|
||
+ "unangenehm.",
|
||
});
|
||
}
|
||
/* HIER STAND "Was du nicht siehst" (entfernt 22.09.2026).
|
||
------------------------------------------------------------------
|
||
Der Hinweis zaehlte auf, was der linken Hand fehlt. Filipe: "das
|
||
geht keinen was an. sie sehen die sachen ja nicht weil es sie
|
||
nichts angeht und das muss auch nicht erwaehnt werden."
|
||
|
||
Die Zeile bleibt als Kommentar stehen, damit niemand den Hinweis
|
||
spaeter "nachtraegt", weil er ihn fuer vergessen haelt. Er ist
|
||
entfernt worden, nicht uebersehen.
|
||
|
||
Dieselbe Ueberlegung steht im Kopf dieser Datei schon: "Eine Liste
|
||
von Dingen, die man nicht darf, ist keine Orientierung, sondern
|
||
eine Kraenkung." Genau das galt fuer diesen Hinweis. */
|
||
if (istDogFather(person)) {
|
||
aus.push({
|
||
titel: "Du entscheidest am Ende",
|
||
text: "Überall, wo dir etwas vorgeschlagen wird, kannst du annehmen "
|
||
+ "oder ablehnen. Ein Vorschlag ist ein Vorschlag.",
|
||
});
|
||
}
|
||
if (person.rolle === "gast") {
|
||
aus.push({
|
||
titel: "Nachts ist Ruhe",
|
||
text: "Zwischen 0 und 6 Uhr schläft der Chat — gelesen werden kann "
|
||
+ "weiter, geschrieben ab 6 Uhr wieder. Das gilt für alle, auch "
|
||
+ "für DogFather.",
|
||
});
|
||
}
|
||
return aus;
|
||
}
|