Files
dogfather-universe/server/workspace-willkommen.js
T
DogFatherGitandClaude Opus 5 150555bc33 Der Notizblock -- und eine Pruefung, die zwoelf Kacheln nie angesehen hat
Filipe: „fuer die modis, rechte und linke hand und dogfather eine
kachel hinzufuegst, sie soll: Notizen, heissen. ich will dass du das
auch wie ein notizblock erstellst. ich will dass es uebelst geil und
einzigartig ist."

=================================================================
TEIL 1: DIE FARBE -- UND WAS DABEI AUFFIEL
=================================================================

Fuer die neue Kachel braucht es einen Ton. Beim Suchen fiel auf, dass
tools/kachelton-entzerren.mjs zwoelf der 45 Farben fuer FREI hielt.

Sie sind es nicht. bereicheFuer() gibt fuer die fuenf Rollen des
Agenturhauses null zurueck -- das heisst „nimm die Liste aus dem
Browser", und die steht in workspace/assets/js/bereiche.js. Dort
stehen die Toene 1 bis 22 und 44: Dashboard, Zahlen, Team-Lage,
Scout-Pipeline. Kacheln, die jeden Tag jemand ansieht.

pruef-kachelfarben hatte denselben blinden Fleck. Sie meldete seit
Wochen „33 benutzte Toene" und war gruen. Was sie dadurch NICHT sah:

  * Der Farblauf von gestern Nacht hat zwoelf dieser Kacheln
    verschoben und drei davon unter die Buntheitsgrenze gedrueckt.
    Gruen geblieben.
  * Ton 7, 16 und 22 lagen SEIT JEHER unter der Grenze (0,112 / 0,116
    / 0,068). Nie gemeldet.

Das ist die Sorte gruener Haken, vor der die Hausregel warnt: Er sagt
nur, dass die Bedingung erfuellt war -- nicht, dass sie das Richtige
angesehen hat.

BEHOBEN, und zwar an der Wurzel: Die REGELN (Kontrast, Buntheit)
gelten jetzt fuer jeden Ton, der irgendwo an einer Kachel steht --
gelesen aus denselben fuenf Dateien, die auch pruef-kachel-universum
liest. Dazu die Namen der Kacheln, damit „Ton 1 traegt Dashboard"
ueberhaupt pruefbar ist.

DIE PALETTE WURDE NEU GERECHNET, mit dem dritten Verfahren an einem
Tag -- die ersten zwei stehen als Fehler im Kopf des Werkzeugs:

  1. Alle 45 neu verteilt. Lief ueber zwei ausdrueckliche Wuensche
     hinweg (#ff1a1a, #a8d8ff).
  2. Nur die Kollisionen, aber immer nur EINEN der beiden bewegt.
     Ergebnis: Ton 1 sprang vom Tuerkis ins Altrosa, 0,197 weit.
  3. BEIDE duerfen sich bewegen, und zwar beide nur ein bisschen.
     Zwei Toene, die 0,02 auseinanderstehen und 0,09 brauchen, teilen
     sich das -- jeder rueckt 0,045, und beide bleiben, was sie waren.

  kleinster Abstand   0,0154  ->  0,0905
  zu blass                 4  ->  0
  sichtbar veraendert           7 Kacheln (ueber 0,05)
  kaum zu sehen                28 (13 zwischen 0,02 und 0,05, 15 darunter)

EINE AUSNAHME MIT ZAHL, keine mit Achselzucken: Ton 1 (Dashboard)
bleibt acht Tausendstel unter der Buntheitsgrenze. Gemessen ueber ALLE
16,7 Millionen sRGB-Farben ist die naechste, die alle Regeln haelt,
#7a75c8 -- ein Blauviolett, 0,113 entfernt. Tuerkis erreicht in sRGB
schlicht keine hoehere Buntheit, und das schmale Band teilen sich
schon sieben Toene. Eine Kachel, die ihre Farbfamilie behaelt, ist die
bessere Antwort auf „richtig geil und speziell" als eine, die acht
Tausendstel bunter ist und niemand wiedererkennt. blassBis sagt jetzt
bei jeder Ausnahme, WIE WEIT sie reicht -- vorher hiess blassErlaubt
schlicht „hier wird weggesehen".

DIE NEUE FARBE IST GRUEN UND WOLLTE BERNSTEIN SEIN. Gesucht war die
Farbe von Papier. Gemessen gibt es sie nicht mehr: Der beste Bernstein
im ganzen Farbraum haelt 0,0799 Abstand -- unter der Hausgrenze. Und
Platz schaffen hilft nicht: Setzt man ihn fest, muss ein warmer Ton
das Band verlassen (Ton 45 waere 0,212 weit ins Magenta gewandert).
Die groesste wirklich freie Luecke liegt im Gruen, bei 0,0917. Ein
linierter Block in Gruen ist Papier, seit es Papier gibt.

=================================================================
TEIL 2: DER BLOCK
=================================================================

DREI ENTSCHEIDUNGEN, AUS DENEN DER REST FOLGT:

1. ES GIBT KEINEN SPEICHERN-KNOPF. Nirgends. Wer einen Block
   aufschlaegt und lostippt, drueckt hinterher nicht auf „sichern";
   er klappt ihn zu. Gesichert wird 800 ms nach dem letzten
   Tastendruck. Geht das nicht, bleibt der Text im Browser liegen und
   wird nachgereicht -- und der Fuss sagt ehrlich „noch nicht
   gesichert", statt einen Verlust zu melden, den man gerade nicht
   verhindern kann.

2. DAS BLATT IST EIN SCHREIBFELD MIT EINER DECKSCHICHT. Das Feld
   traegt Text und Cursor und ist unsichtbar; darueber zeichnet eine
   zweite Schicht denselben Text noch einmal -- mit Kaestchen,
   Ueberschriften, Strichen und Links.

   DARAUS FOLGT EINE EISERNE REGEL, und sie steht dreimal im Code:
   KEINE Auszeichnung darf die BREITE eines Zeichens aendern. Kein
   Fettdruck, keine andere Groesse, keine Sperrung. Erlaubt sind nur
   Farbe, Flaeche, Rahmen und Durchstreichen. Ein einziges
   font-weight: 700 verschoebe den Umbruch, und ab der zweiten Zeile
   stuende der sichtbare Text neben dem Cursor.

   pruef-notizen misst das am echten Umbruch: eine Probe mit allen
   Auszeichnungen und einer Zeile, die umbrechen MUSS. Sind beide
   Schichten verschieden hoch, sitzt der Umbruch woanders. Mit
   Gegenprobe -- Fettdruck auf der Deckschicht bricht die Deckung um
   genau eine Zeile (30 px), und die Zeile wird rot.

3. DIE TASTATUR FUEHRT DIE LISTE FORT, NICHT EINE LEISTE. Ein
   Spiegelstrich und Enter macht den naechsten Punkt, ein leeres
   Kaestchen und Enter das naechste Kaestchen -- und ein GESETZTER
   Haken wird dabei nicht mitgenommen, die naechste Aufgabe ist ja
   noch nicht erledigt. Zweimal Enter beendet die Liste. Ein Klick
   aufs Kaestchen hakt ab. Es gibt keine Werkzeugleiste, weil man beim
   Schreiben nie den Stift wechselt.

UND: NIEMAND SIEHT DIE NOTIZEN EINES ANDEREN. Auch DogFather nicht.
Es gibt in workspace-notizen.js keinen siehtAlles()-Zweig, keinen
Umschalter, keine fremde Liste -- jede Abfrage hat person_id = ? fest
eingebaut. Die Kachel steht deshalb in „Fuer dich" und nicht in
„Taeglich": Die Gruppe sagt die wichtigste Eigenschaft, bevor man sie
anfasst.

NUR AUF DER TEAM-ADRESSE. Auf der Agenturadresse ist die Seite ein
404 -- auch fuer DogFather. Das ist dieselbe Trennung wie bei Chat,
Kalender und Aufgaben, und sie steht an zwei Stellen: in
GEHOERT_ZU_ADRESSE (die Datei) und in der Schnittstelle (die Daten).
Die Seite ist nur HTML; die Daten sind die Sache.

Dazu: sechs Papierfarben, Anheften, Suche mit Hervorhebung im Text,
Papierkorb mit dreissig Tagen und einem Eintrag in der
Aufbewahrungsliste (ohne den waere die Frist ein Satz in einem
Kommentar -- genau das ist dem Support am 24.09. passiert).

=================================================================
EIN FUND BEIM BAUEN, DER ALLEN GEHOERT
=================================================================

DELETE /workspace/api/notizen/:id gab es schon -- in
workspace-aufgaben.js, fuer die Notizen AN einer Aufgabe. Weil jener
Router frueher eingehaengt ist, hat er jeden Loeschversuch des Blocks
abgefangen und mit 404 beantwortet.

Von aussen sah das aus wie ein Fehler im neuen Modul: Anlegen ging,
Aendern ging, Loeschen nicht. Man sucht dann im eigenen Code, und dort
ist nichts. Express meldet so etwas nicht -- es nimmt die erste Route,
die passt, und schweigt ueber die zweite.

Der Block liegt jetzt unter /workspace/api/notizblock. Und
pruef-notizen geht seitdem den Routenbaum von express durch und meldet
jedes Paar aus Methode und Pfad, das zweimal vergeben ist. Gemessen:
307 Wege, keine Dublette. Die Pruefung gilt fuers ganze Haus, auch
wenn sie in dieser Datei steht -- hier ist sie gefunden worden.

Nebenbei berichtigt: pruef-crew-adresse verlangte von jedem Eintrag
der Haustafel HTTP 200 mit Inhalt. Das stimmte, solange dort nur
oeffentliche Dateien standen. notizen.html ist die erste Seite hinter
der Anmeldung -- sie antwortet mit 302, und das ist richtig. Gefragt
wird jetzt, was die Tafel wirklich meint: GIBT es das hier.

GEMESSEN, alles nach den Aenderungen:

  pruef-notizen             76 Punkte, 0 Fehler (neu)
  pruef-kachelfarben        31 Punkte, 0 Fehler (vorher 26)
  pruef-kachel-universum    13 Punkte, 0 Fehler
  pruef-crew-adresse       157 Punkte, 0 Fehler
  pruef-buehne             230 Punkte, 0 Fehler -- notizen.html neu in
                           der Liste, schlechtester Kontrast 6,73:1,
                           also 50 % ueber der Grenze
  pruef-rechtetafel, -haus-seiten, -struktur, -css-klassen,
  -jeder-hat-eine-seite, -workspace-seiten, -kachelraster,
  -rueckmeldung            alle 0 Fehler

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-26 12:42:59 +02:00

294 lines
13 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.
/* =====================================================================
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;
}