Files
dogfather-universe/server/workspace-befinden-resuemee.js
DogFatherGitandClaude Opus 5 95c4599baa Anfuehrungszeichen: 199 falsche Schlusszeichen im sichtbaren Text
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]>
2026-10-01 01:56:15 +02:00

335 lines
15 KiB
JavaScript
Raw Permalink 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.
/* =====================================================================
workspace-befinden-resuemee.js — Eine Zusammenfassung am Ende
(20.09.2026)
Filipe: „und am ende von allen fragen will ich eine gesamte
zusammenfassung, nicht bei jeder frage oder kategorie eine, sondern
EINE gesamte am ende. eine motivierende zusammenfassung für die
person. hol die beste der beste möglichkeiten dafür die man beutzen
kann kostenlos."
---------------------------------------------------------------------
DIE FRAGE NACH DER „BESTEN KOSTENLOSEN MÖGLICHKEIT" — EHRLICH
BEANTWORTET
Naheliegend wäre eine der kostenlosen KI-Schnittstellen (Groq,
Gemini, HuggingFace). Sie wäre hier die FALSCHE Wahl, und zwar aus
drei Gründen, von denen jeder einzelne reicht:
1. ES SIND GESUNDHEITSDATEN. Die Antworten sagen, wer nachts wach
liegt, wer angefeindet wird, wer ans Aufhören denkt. Genau
deshalb sind sie im ganzen Haus als `nurSelbst` markiert und
liegen in einer eigenen Tabelle, die kein anderer Weg liest.
Sie an einen fremden Dienst zu schicken, damit er einen netten
Satz daraus baut, würde diese ganze Sorgfalt in einer Zeile
aufheben. Kostenlose KI-Angebote bezahlt man mit den Daten —
in ihren Bedingungen steht meist ausdrücklich, dass Eingaben
zum Training verwendet werden dürfen.
2. EIN SPRACHMODELL ERFINDET. Es schreibt ohne Weiteres „du hast
dich diesen Monat deutlich erholt", wenn das Gegenteil dasteht,
und es klingt dabei überzeugend. Eine Zusammenfassung über das
eigene Befinden, die etwas Falsches behauptet, ist schlimmer
als keine: Man glaubt ihr ja, und beim nächsten Mal glaubt man
ihr nicht mehr — auch dann nicht, wenn sie recht hat.
3. SIE FÄLLT AUS. Ein fremder Dienst hat Störungen, Kontingente
und Kündigungen. Dieser Text hier funktioniert in zehn Jahren
noch, ohne Schlüssel, ohne Rechnung, ohne Netz.
DIE BESTE KOSTENLOSE MÖGLICHKEIT IST DESHALB KEINE KI, SONDERN
SORGFÄLTIG GESCHRIEBENE SPRACHE ÜBER GERECHNETEN ZAHLEN. Sie läuft
auf Filipes eigenem Server, kostet nichts, lügt nie und kann nicht
ausfallen. Was ein Sprachmodell hier besser könnte — Abwechslung im
Wortlaut — ist gegen die drei Punkte oben kein Argument.
---------------------------------------------------------------------
WIE MAN MOTIVIERT, OHNE ZU LÜGEN
Das ist die eigentliche Schwierigkeit. Eine Zusammenfassung, die bei
sechs von acht „da hakt es" fröhlich bleibt, ist keine Ermutigung,
sondern Hohn — und beim nächsten Mal antwortet man ihr nicht mehr
ehrlich. Drei Regeln, und sie gelten in jeder Lage:
ZUERST, WAS TRÄGT — und zwar benannt, nicht als Zahl. „Dass du
nach der Schicht abschalten kannst, ist das, was das hier
zusammenhält" sagt etwas. „5 von 8 grün" sagt nichts.
DANN HÖCHSTENS EINE SACHE. Wer fünf Probleme auf einmal vorgelegt
bekommt, sieht keine fünf Aufgaben, sondern einen Berg. Die Liste
aller Punkte steht ohnehin darüber; hier steht die EINE, bei der
sich das Hinsehen am meisten lohnt.
UND EIN SCHLUSS, DER ZUR LAGE PASST. Nicht „weiter so" für alle.
Wer gerade viel trägt, braucht einen anderen letzten Satz als
jemand, bei dem alles läuft.
---------------------------------------------------------------------
KEINE ZAHLEN, DIE JEMAND PFLEGEN MUSS
Jede Zahl in diesem Text wird gerechnet — auch „acht Fragen" und
„sechs Felder". Ein fester Satz „alle sechs Felder sind ruhig" wäre
beim siebten Feld still falsch, und niemand würde es merken.
===================================================================== */
/** Welche Stufe zählt als „läuft", welche als „hakt".
*
* Abgeleitet aus dem Schlüssel und nicht als eigene Liste gepflegt:
* `unklar` ist ausdrücklich WEDER gut noch schlecht — „kann ich nicht
* sagen" ist eine ehrliche Antwort und darf nicht als halbes Problem
* gezählt werden. Wer sie zu den Sorgen schlägt, erzeugt Sorgen, die
* niemand geäußert hat. */
const TRAEGT = new Set(["laeuft"]);
const WAECHST = new Set(["waechst"]);
const HAKT = new Set(["hakt"]);
/** Ein Satzende ohne doppelten Punkt. */
const satz = (t) => {
const x = String(t || "").trim();
if (!x) return "";
return /[.!?…]$/.test(x) ? x : x + ".";
};
/** Der erste Buchstabe klein — für Halbsätze, die angehängt werden.
* (Deutsche Substantive bleiben groß; deshalb nur dort benutzt, wo
* ein Verb oder Pronomen folgt.) */
const klein = (t) => (t ? t[0].toLowerCase() + t.slice(1) : t);
/**
* Die eine Zusammenfassung am Ende einer Runde.
*
* @param antworten { schluessel: stufe } — die Antworten DIESER Runde
* @param punkte Die Fragen dieser Runde (mit titel, feld, kern)
* @param felder BEFINDEN_FELDER
* @param vorher Die Antworten der VORIGEN Runde, oder null
* @returns {{moeglich, kopf, traegt, thema, schluss, zahlen}}
*/
export function resuemeeVon(antworten, punkte, felder, vorher = null, einordnung = {}) {
const gefragt = punkte.filter((p) => antworten?.[p.schluessel]);
/* DRITTER AUSGANG. Ohne Antworten gibt es nichts zusammenzufassen --
und das muss dastehen, statt dass ein freundlicher Satz über
nichts erscheint. */
if (!gefragt.length) {
return {
moeglich: false,
grund: "Noch keine Antworten in dieser Runde.",
kopf: null, traegt: [], thema: null, schluss: null,
zahlen: { gefragt: 0, traegt: 0, waechst: 0, hakt: 0, unklar: 0, felder: 0 },
};
}
const mit = (menge) => gefragt.filter((p) => menge.has(antworten[p.schluessel]));
const traegtP = mit(TRAEGT);
const waechstP = mit(WAECHST);
const haktP = mit(HAKT);
const unklarP = gefragt.length - traegtP.length - waechstP.length - haktP.length;
const feldName = (k) => felder.find((f) => f.schluessel === k)?.name || k;
/* ---- WELCHES FELD IST DAS THEMA? --------------------------------
Nicht die erste Frage mit „hakt", sondern das FELD mit den
meisten. Drei Haken quer über alles heißen „schwerer Monat";
drei Haken in EINEM Feld heißen „hier ist etwas". Nur das zweite
ist ein Befund, und nur darüber lohnt ein Satz.
Bei Gleichstand entscheidet die Reihenfolge der Felder, und die
geht von außen nach innen — das schwerere Thema gewinnt. */
const proFeld = new Map();
for (const p of haktP) {
proFeld.set(p.feld, (proFeld.get(p.feld) || 0) + 1);
}
let themaFeld = null, themaN = 0;
for (const f of felder) {
const n = proFeld.get(f.schluessel) || 0;
if (n > themaN) { themaFeld = f; themaN = n; }
}
/* ---- WAS TRÄGT ---------------------------------------------------
Höchstens zwei, und benannt. Eine Aufzählung von fünf Dingen liest
niemand; zwei bleiben hängen. Kernfragen zuerst — sie sind die,
die in jeder Runde gestellt werden, und damit die, deren „läuft"
am meisten bedeutet. */
const traegtListe = [...traegtP]
.sort((a, b) => (b.kern ? 1 : 0) - (a.kern ? 1 : 0))
.slice(0, 2)
.map((p) => ({ schluessel: p.schluessel, titel: p.titel, feld: p.feld }));
/* ---- HAT SICH SEIT DEM LETZTEN MAL ETWAS GEBESSERT? -------------
Nur echte Verbesserungen, und nur bei Fragen, die BEIDE Male
gestellt wurden. Seit die Fragen rotieren, wäre „war letztes Mal
nicht dabei" sonst eine Verbesserung — und das ist keine. */
const besser = [];
if (vorher) {
const rang = { hakt: 0, waechst: 1, laeuft: 2 };
for (const p of gefragt) {
const a = rang[vorher[p.schluessel]];
const b = rang[antworten[p.schluessel]];
if (a !== undefined && b !== undefined && b > a) besser.push(p);
}
}
/* ---- DER KOPFSATZ ------------------------------------------------
Vier Lagen, vier Sätze — und keiner davon behauptet etwas, das
nicht in den Zahlen steht. */
const n = gefragt.length;
let kopf;
if (!haktP.length && !waechstP.length) {
kopf = unklarP === n
? "Diesmal konntest du zu fast allem nichts sagen. Das ist eine ehrliche "
+ "Antwort – und beim nächsten Mal weißt du vermutlich mehr."
: `Alle ${n} Fragen dieser Runde stehen auf „Läuft“. Das ist selten, und `
+ "es ist kein Zufall – irgendwas machst du gerade richtig.";
} else if (!haktP.length) {
kopf = `Nichts hakt gerade. ${waechstP.length === 1 ? "Eine Sache wächst" : `${waechstP.length} Sachen wachsen`} `
+ "noch – das heißt, sie sind unterwegs, nicht liegengeblieben.";
} else if (haktP.length === 1) {
kopf = `Von ${n} Fragen hakt genau eine. ${n - 1} laufen oder wachsen – `
+ "das ist der Boden, auf dem du stehst, und der ist tragfähig.";
} else if (haktP.length < n / 2) {
kopf = `${haktP.length} von ${n} Sachen haken gerade. Die anderen ${n - haktP.length} `
+ "nicht – und das ist die Hälfte der Auskunft, die man beim Draufschauen "
+ "meistens übersieht.";
} else {
/* DIE SCHWERE LAGE. Hier NICHT aufmunternd werden -- das ist der
Moment, in dem eine fröhliche Zusammenfassung alles Vertrauen
kostet. Stattdessen: benennen, entlasten, und den Weg zeigen.
„Fast nie an dir" ist keine Beschönigung, sondern das, was die
Forschung zu Belastung in Ehrenämtern durchgehend findet. */
kopf = `${haktP.length} von ${n} Sachen haken. Das ist viel, und es liegt `
+ "fast nie an der Person, die es aushält. Es ist ein guter Moment, "
+ "das jemandem zu sagen, statt es noch eine Runde mitzunehmen.";
}
/* ---- DER SCHLUSSSATZ ---------------------------------------------
Er gehört zur Lage. „Weiter so" für alle wäre ein Satz, der nie
jemanden meint. */
let schluss;
if (besser.length) {
schluss = besser.length === 1
? `Und etwas hat sich seit dem letzten Mal gebessert: ${klein(besser[0].titel)}. `
+ "Das hat sich nicht von allein gemacht."
: `Und ${besser.length} Sachen haben sich seit dem letzten Mal gebessert. `
+ "Das hat sich nicht von allein gemacht.";
} else if (haktP.length >= n / 2) {
schluss = "Du musst das hier nicht allein lösen. Dass du es aufgeschrieben "
+ "hast, ist der erste Teil – der zweite ist ein Satz an jemanden.";
} else if (haktP.length) {
schluss = "Das musst du heute nicht lösen. Es reicht, dass du es weißt – "
+ "beim nächsten Mal siehst du, ob es geblieben ist.";
} else {
schluss = "Nächste Runde in zwei Wochen. Bis dahin: nichts zu tun.";
}
/* ---- EIN SCHRITT, NICHT NEUN (22.09.2026) ------------------------
Filipe: "das resume unten auf jeder frage hab ich auch schon
mehrmals gesagt muss weg ich will ein ganzes gesamtresume ...
aber auch individuel so dass jeder sich selber auch verbessert
und steigert fuers team."
Vorher stand unter JEDER Frage eine Einordnung und ein "Was
hilft" -- bei neun Fragen neun Ratschlaege auf einmal. Das ist
keine Hilfe, sondern eine Maengelliste mit Fussnoten. Wer neun
Dinge gleichzeitig angehen soll, geht keins an.
Jetzt genau EINER: der zu der Sache, die am meisten haengt.
Kernfragen zuerst -- sie werden in jeder Runde gestellt, ihr
"hakt" wiegt also schwerer als das einer Zusatzfrage, die alle
vier Runden vorbeikommt.
Und wenn nichts hakt, ist der Schritt trotzdem einer: dann der
zu der Sache, die noch WAECHST. "Nichts zu tun" ist fuer jemanden,
bei dem alles laeuft, der richtige Satz -- aber nur dann. */
const schrittKandidaten = haktP.length ? haktP : waechstP;
const schrittP = [...schrittKandidaten]
.sort((a, b) => (b.kern ? 1 : 0) - (a.kern ? 1 : 0))[0] || null;
const e = schrittP ? (einordnung[schrittP.schluessel] || {}) : {};
const schritt = schrittP && e.hilft
? {
titel: schrittP.titel,
was: e.hilft,
/* Die Einordnung aus der Forschung steht EINMAL da, nicht
neunmal -- als Grund, warum dieser Schritt der richtige ist.
Ohne sie waere es ein Ratschlag; mit ihr ist es einer, den
jemand nachgeprueft hat. */
welt: e.welt || null,
haengt: !!haktP.length,
}
: null;
/* ---- WAS DAS TEAM DAVON HAT --------------------------------------
Filipe: "mega geile super team loesungen findet aber auch
individuel".
DER SATZ WIRD ABGELEITET, NICHT ERFUNDEN. Ein Team-Spruch, der
immer derselbe ist, wird beim dritten Mal ueberlesen -- und ein
"gemeinsam schaffen wir das" an jemanden, bei dem gerade fuenf
von neun Sachen haken, ist das Gegenteil von Hilfe.
Vier Lagen, vier verschiedene Saetze. Und in der schwersten ist
der Team-Bezug KEINE Aufforderung, sondern eine Entlastung: Wer
viel traegt, braucht nicht zu hoeren, dass er auch noch etwas
fuers Team tun soll. */
let team;
if (haktP.length >= n / 2 && n > 0) {
team = "Im Team ist gerade nichts von dir gefragt. Wenn du magst, sag "
+ "einem Menschen, wie es steht – das ist alles, und es reicht.";
} else if (haktP.length) {
team = themaFeld && themaN > 1
? `Wahrscheinlich bist du damit nicht allein: „${feldName(themaFeld.schluessel)}“ `
+ "hakt selten nur bei einem. Es in der Runde anzusprechen löst es oft "
+ "für mehrere auf einmal."
: "Eine Sache, die bei dir hakt, hakt selten nur bei dir. Wer sie ausspricht, "
+ "löst sie oft für mehr als sich selbst.";
} else if (traegtListe.length) {
team = `Was bei dir gerade trägt – ${klein(traegtListe[0].titel)} – ist genau das, `
+ "wonach jemand anderes gerade sucht. Zwei Sätze darüber in der Runde sind "
+ "mehr wert als jede Anleitung.";
} else {
team = "Wenn du in der nächsten Runde etwas merkst, das anderen helfen würde: "
+ "sag es. Das ist der kürzeste Weg, wie aus einzelnen Leuten ein Team wird.";
}
return {
moeglich: true,
kopf,
schritt,
team,
traegt: traegtListe,
thema: themaFeld && themaN > 0
? {
feld: themaFeld.schluessel,
name: feldName(themaFeld.schluessel),
anzahl: themaN,
/* MEHRERE IN EINEM FELD SIND ETWAS ANDERES ALS EINES. Der Satz
sagt, welches von beidem es ist -- daran hängt, ob man mit
einer Sache spricht oder mit einem Thema. */
satz: themaN > 1
? satz(`Wenn du nur an einer Stelle hinschaust, dann hier: „${feldName(themaFeld.schluessel)}“ `
+ `– da stehen ${themaN} der Haken beieinander. Das ist selten Zufall`)
: satz(`Wenn du nur an einer Stelle hinschaust, dann hier: „${feldName(themaFeld.schluessel)}“`),
/* DIE TITEL NUR, WENN ES MEHRERE SIND (22.09.2026).
Bei genau einem stand er zweimal untereinander: einmal hier
und gleich darunter als "Dein naechster Schritt". Im Bild
sah das aus wie ein Fehler -- gefunden hat es kein Messwert,
sondern der Blick auf den Bildschirm. */
titel: themaN > 1
? haktP.filter((p) => p.feld === themaFeld.schluessel).map((p) => p.titel)
: [],
}
: null,
schluss,
zahlen: {
gefragt: n,
traegt: traegtP.length,
waechst: waechstP.length,
hakt: haktP.length,
unklar: unklarP,
besser: besser.length,
/* GERECHNET, nicht eingetragen: Wer ein siebtes Feld ergänzt,
bekommt hier automatisch die 7. */
felder: new Set(gefragt.map((p) => p.feld).filter(Boolean)).size,
},
};
}