Rueckmeldung in beide Richtungen (Kapitel 5 und 6)

Filipe: "Nicht nur ich soll meine Modis bewerten oder ihnen Feedback
geben koennen. Auch die Modis sollen mir Feedback geben koennen. Sie
sollen mir beispielsweise sagen koennen: Was koennte ich verbessern?"
Und der Schlusssatz seines Pflichtenhefts: "Es soll nicht nur dazu
dienen, Leistungen zu kontrollieren. Es soll vor allem dabei helfen,
als Team besser zu werden."

EIN BRETT FUER BEIDE KAPITEL, NICHT ZWEI. Kapitel 5 (gegenseitiges
Feedback) und Kapitel 6 (gemeinsame Reflexion) stellen dieselben
Fragen -- "was laeuft gut, was laeuft schlecht, was fehlt" --, einmal
an eine Person und einmal an das Team. Zwei Bretter haetten bedeutet,
dass man beim Schreiben zuerst entscheiden muss, an WEN es geht, bevor
man weiss, WAS man sagen will. Hier ist es umgekehrt: erst die Sache,
dann die Richtung.

DIE NEUN FRAGEN SIND SEINE, wortwoertlich aus dem Pflichtenheft
zusammengezogen: laeuft gut · laeuft nicht gut · unbedingt behalten ·
an DogFather · was dem Team fehlt · Regel aendern · besser organisieren
· Idee · Wunsch fuer spaeter. Sie stehen als feste Faecher da und nicht
als freies Feld -- genau das ist der Unterschied zwischen einer
Sammlung und einem Haufen: Neun Faecher kann man auswerten, tausend
Formulierungen nicht.

KEIN NEUER BAUKASTEN. Die Rueckmeldung ist ein BEREICH wie das
Ideen-Board: dieselbe Tabelle, dieselbe Seite, dieselben Regeln fuers
Anlegen, Aendern und Loeschen, dieselbe Zugangssperre. Ein eigenes
Modul haette all das ein zweites Mal gebraucht -- und die zweite
Fassung waere die gewesen, in der eine Regel fehlt.

DIE EINE NEUE SACHE IST DIE RICHTUNG. Beim Schreiben waehlt man
zwischen "fuers Team" (Vorgabe) und "nur an DogFather". Ohne diese Wahl
haette man eines von beidem verloren: Wer "was koenntest du besser
machen" vor versammelter Mannschaft sagen muss, sagt es nicht -- wer
alles nur unter vier Augen sagen kann, hat kein Team-Gespraech.

UND "NUR DOGFATHER" HEISST NUR DOGFATHER -- die rechte Hand
ausdruecklich nicht. Sie sieht sonst ueberall dasselbe wie er; hier
nicht, weil das Etikett sonst nicht stimmen wuerde. Eine Zusage mit
einer Ausnahme im Kleingedruckten ist keine. Sie schreibt selbst
genauso -- auch ueber ihn.

DIE ZUSAGE STEHT IN sichtbarEintrag(), also in derselben Funktion, durch
die auch das Lesen einer einzelnen Zeile, das Aendern und das Loeschen
gehen. Eine Regel, die nur die Liste filtert, laesst die Zeile ueber
ihre Nummer trotzdem heraus; die Pruefung klopft deshalb auch von
hinten (PATCH und DELETE auf die vertrauliche Zeile: 404, auf die
offene: 200).

`COALESCE(nur_leitung, 0)`: Jede Zeile, die es vor heute gab, hat dort
NULL, und in SQL ist `NULL = 0` nicht falsch, sondern UNBEKANNT. Ohne
den Ersatzwert waere der gesamte alte Bestand von einer Minute auf die
andere unsichtbar gewesen -- und niemand haette es gemeldet, denn ein
leeres Brett sieht nicht nach Fehler aus. Beide Richtungen sind
gemessen.

KEINE ANONYMITAET, und das ist eine Entscheidung, keine Luecke. In
einem Team dieser Groesse waere sie ohnehin keine: An drei Saetzen
erkennt jeder jeden. Ein Versprechen, das nicht haelt, ist schlimmer
als keines.

DER FARBTON DER KACHEL IST AUSGERECHNET, NICHT AUSGESUCHT. Bei 24
vorhandenen Toenen landet ein neuer fast zwangslaeufig neben einem
alten, und zwei Kacheln in FAST derselben Farbe sind schlimmer als in
derselben -- bei gleicher merkt man den Fehler, bei fast gleicher sucht
man ihn. Alle 24 wurden in Farbwinkel umgerechnet; die groesste Luecke
liegt zwischen 90 und 160 Grad und ist 70 Grad breit, mehr als doppelt
so viel wie die naechste. Der neue Ton sitzt in ihrer Mitte, 8,9 zu 1
auf dunklem Grund.

pruef-rueckmeldung 34 (neu) · pruef-rollen 274 -> 277 ·
pruef-start-ansicht zaehlt jetzt 25 Kacheln und 25 Farben (sie zaehlt
selbst, statt eine Zahl festzuhalten -- deshalb blieb sie gruen) ·
pruef-modi-ideen 30 · pruef-modi-verborgen 78 · pruef-css-klassen gruen
· pruef-modi-wortleck 5.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-10 22:54:04 +02:00
co-authored by Claude Opus 5
parent 72b36d1c31
commit be121a483e
30 changed files with 850 additions and 293 deletions
+113 -8
View File
@@ -146,6 +146,62 @@ export const BEREICHE = {
DIE ARTEN SIND DIE DREI AUS DEM DOKUMENT, dazu "Sonstiges": Eine
Idee, die in keine der drei passt, soll nicht ungeschrieben
bleiben, weil das Formular sie nicht kennt. */
/* =====================================================================
DIE RUECKMELDUNG (10.09.2026, Kapitel 5 und 6 des Pflichtenhefts)
Filipe: "Nicht nur ich soll meine Modis bewerten oder ihnen Feedback
geben koennen. Auch die Modis sollen mir Feedback geben koennen."
Und: "Es soll nicht nur dazu dienen, Leistungen zu kontrollieren. Es
soll vor allem dabei helfen, als Team besser zu werden."
EIN BEREICH FUER BEIDE KAPITEL, NICHT ZWEI. Kapitel 5 (gegenseitiges
Feedback) und Kapitel 6 (gemeinsame Reflexion) stellen dieselben
Fragen -- "Was laeuft gut? Was laeuft schlecht? Was fehlt?" --, nur
einmal an eine Person und einmal an das Team. Zwei Bretter dafuer
haetten bedeutet, dass man beim Schreiben zuerst entscheiden muss,
an WEN es geht, bevor man weiss, WAS man sagen will. Hier ist es
umgekehrt: erst die Sache, dann die Richtung (siehe nur_leitung).
DIE NEUN FRAGEN SIND SEINE, wortwoertlich aus dem Pflichtenheft
zusammengezogen. Sie stehen als "Arten" da und nicht als freies
Textfeld, weil genau das der Unterschied zwischen einer Sammlung
und einem Haufen ist: Neun Faecher kann man auswerten, tausend
Formulierungen nicht.
KEINE ANONYMITAET, und das ist eine Entscheidung, keine Luecke. In
einem Team dieser Groesse waere sie ohnehin keine -- an drei
Saetzen erkennt jeder jeden. Ein Versprechen, das nicht haelt, ist
schlimmer als keines. Was es stattdessen gibt, ist die Wahl
zwischen "fuers Team" und "nur an DogFather". */
rueckmeldung: {
name: "Rückmeldung",
/* Wie beim Ideen-Board: Das hier ist keine Akte ueber jemanden,
sondern etwas, das allen gehoert. */
ober: "Gemeinsam besser werden",
arten: {
gut: "Läuft gut",
haken: "Läuft nicht gut",
behalten: "Unbedingt behalten",
dogfather: "An DogFather",
team: "Was dem Team fehlt",
regeln: "Regel ändern",
ablauf: "Besser organisieren",
idee: "Idee",
zukunft: "Wunsch für später",
},
bewertung: false,
/* Dringlichkeit ja: "Der Ton war gestern kaputt" ist etwas anderes
als "waere schoen, irgendwann". Eine Sammlung ohne Rangfolge
wird nach vier Wochen nicht mehr gelesen. */
dringlichkeit: true,
ohneCreatorBezug: true,
/* Schaltet das Feld "nur an DogFather" frei -- siehe pruefe() und
sichtbarEintrag(). Ohne dieses Merkmal wird es in jedem anderen
Bereich stillschweigend verworfen, wie Bewertung und
Dringlichkeit auch. */
vertraulich: true,
},
ideen: {
name: "Ideen-Board",
/* DAS WORT UEBER DEM TITEL. Ueberall sonst steht dort "Betreuung",
@@ -276,7 +332,7 @@ bereicheRouter.use("/workspace/api/bereich", angemeldet);
ZWEI SCHLOESSER, ABSICHTLICH: Hier haengt der ZUGANG zur Seite,
sichtbar() haengt die Bedingung an die ZEILEN. Faellt eines weg,
haelt das andere. */
for (const geschuetzt of ["ideen", "angebote"]) {
for (const geschuetzt of ["ideen", "angebote", "rueckmeldung"]) {
bereicheRouter.use(`/workspace/api/bereich/${geschuetzt}`, (req, res, next) => {
if (siehtModis(req.person)) return next();
return res.status(404).json({ fehler: "nicht_gefunden" });
@@ -482,11 +538,41 @@ function sichtbarRoh(person) {
`praefix` ist da, weil die Suche dieselbe Tabelle unter demselben
Kuerzel "e" fuehrt -- geht das eines Tages auseinander, faellt es
beim Aufruf auf, nicht erst im Betrieb. */
/* VERTRAULICHE RUECKMELDUNGEN SIEHT NUR DOGFATHER -- UND DER SCHREIBER
(10.09.2026).
Wer beim Schreiben "nur an DogFather" waehlt, muss sich darauf
verlassen koennen. Deshalb steht die Bedingung HIER, in derselben
Funktion, durch die auch das Lesen einer einzelnen Zeile, das Aendern
und das Loeschen gehen -- nicht in der Listenabfrage. Eine Regel, die
nur die Liste filtert, laesst die Zeile ueber ihre Nummer trotzdem
heraus.
"NUR DOGFATHER" HEISST AUCH NICHT DIE RECHTE HAND. Sie sieht sonst
ueberall dasselbe wie er. Hier nicht, und zwar deshalb, weil das
Etikett sonst nicht stimmte: Eine Zusage mit einer Ausnahme im
Kleingedruckten ist keine. Sie kann selbst genauso schreiben -- auch
ueber ihn, und dann sieht er es nicht anders als jeder andere.
`COALESCE(..., 0)`: Alle Zeilen, die es vor heute gab, haben in
dieser Spalte NULL. Ohne den Ersatzwert waere `nur_leitung = 0`
fuer sie UNBEKANNT statt wahr -- und der gesamte alte Bestand waere
von einer Minute auf die andere unsichtbar gewesen. Ein Loch, das
niemand meldet: Ein leeres Brett sieht nicht nach Fehler aus. */
function ohneVertrauliche(regel, person, praefix) {
if (!regel || istDogFather(person)) return regel;
return {
wo: `(${regel.wo}) AND (COALESCE(${praefix}.nur_leitung, 0) = 0`
+ ` OR ${praefix}.erstellt_von = ?)`,
werte: [...regel.werte, person.id],
};
}
export function sichtbarEintrag(person, praefix = "e") {
const regel = sichtbar(person);
if (!regel) return regel;
if (regel.wo === "1=1") return regel; // DogFather sieht ohnehin alles
if (!BEREICHE_FUER_ALLE.length) return regel;
if (!BEREICHE_FUER_ALLE.length) return ohneVertrauliche(regel, person, praefix);
const liste = BEREICHE_FUER_ALLE.map(() => "?").join(", ");
const mitAllen = {
wo: `(${praefix}.bereich IN (${liste}) OR ${regel.wo})`,
@@ -499,11 +585,11 @@ export function sichtbarEintrag(person, praefix = "e") {
kaeme durch, egal wem er gehoert. Heute hat kein Modi eine Kachel
dorthin; ein Aufruf an der Oberflaeche vorbei braucht sie aber
nicht. Eine Regel, die nur im Formular gilt, ist keine Regel. */
if (siehtModis(person)) return mitAllen;
return {
if (siehtModis(person)) return ohneVertrauliche(mitAllen, person, praefix);
return ohneVertrauliche({
wo: `(${mitAllen.wo}) AND ${ohneTeamDogi(praefix)}`,
werte: mitAllen.werte,
};
}, person, praefix);
}
const SPALTEN = `
@@ -515,6 +601,13 @@ const SPALTEN = `
/* Was DogFather dafuer tun muesste -- nur bei den Angeboten
gefuellt, siehe BEREICHE.angebote. */
e.einsatz,
/* 1 = nur DogFather darf das lesen. Die Spalte MUSS mitkommen, auch
wenn die Regel unten schon dafuer sorgt, dass niemand Fremdes die
Zeile bekommt: Der Schreibende soll an seiner eigenen Zeile sehen,
dass sie vertraulich ist. Ohne diese Angabe saehe sie aus wie jede
andere -- und beim naechsten Mal schriebe er offen, was er
vertraulich meinte. */
e.nur_leitung,
${externSql("pc.name", "e.creator_extern")} AS creator_name,
pe.name AS erstellt_name,
(SELECT name FROM content_saeulen s WHERE s.id = e.saeule_id) AS saeule_name,
@@ -609,6 +702,13 @@ function pruefe(bereich, körper, { neu }) {
if (!DRINGLICHKEITEN.includes(körper.dringlichkeit)) fehler.push("Unbekannte Dringlichkeit.");
else aus.dringlichkeit = körper.dringlichkeit;
}
/* Nur dort, wo der Bereich es vorsieht -- sonst koennte man einen
Schutzvorfall oder eine Content-Idee als "vertraulich" markieren
und damit vor den eigenen Leuten verstecken. */
if (einstellung.vertraulich && körper.nur_leitung !== undefined) {
aus.nur_leitung = (körper.nur_leitung === true || körper.nur_leitung === 1
|| körper.nur_leitung === "1") ? 1 : 0;
}
/* Die vier Content-Felder gibt es nur in der Content-Planung. In jedem
anderen Bereich werden sie stillschweigend verworfen -- wie Bewertung
@@ -771,14 +871,19 @@ bereicheRouter.post("/workspace/api/bereich/:bereich", gleicheHerkunft, (req, re
INSERT INTO eintraege
(bereich, art, titel, text, datum, bewertung, dringlichkeit, status,
creator_id, creator_extern, erstellt, erstellt_von, hook, format, saeule_id, geplant,
event_ende, event_aufgaben, event_regeln, einsatz)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(
event_ende, event_aufgaben, event_regeln, einsatz, nur_leitung)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(
bereich, aus.art, aus.titel, aus.text ?? null, aus.datum,
aus.bewertung ?? null, aus.dringlichkeit ?? "mittel", aus.status ?? "offen",
aus.creator_id ?? null, aus.creator_extern ?? null, jetzt(), req.person.id,
aus.hook ?? null, aus.format ?? null, aus.saeule_id ?? null, aus.geplant ?? null,
aus.event_ende ?? null, aus.event_aufgaben ?? null, aus.event_regeln ?? null,
aus.einsatz ?? null);
aus.einsatz ?? null,
/* 0 und nicht NULL als Vorgabe: NULL hiesse "unbekannt", und
unbekannt ist bei einer Vertraulichkeit die falsche Antwort.
Wer nichts waehlt, schreibt fuers Team -- das ist der offene
Fall, und der soll der Normalfall sein. */
aus.nur_leitung ?? 0);
protokolliere("eintrag_angelegt", {
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),