Files
dogfather-universe/workspace/assets/js/entwicklung.js
T
DogFatherGitandClaude Opus 5 d4730deb30 Wie geht's dir? bekommt eine eigene Seite
Filipe, zum Bildschirmfoto der Kachel: "ich will dass du diese seite
perfektionnierst den gerade wenn ich drauf druecke geht die
entwicklungsseite auf."

Der Fehler war schlimmer als ein falscher Verweis. Auf der Kachel
steht "die Antworten sieht nur du" -- und sie oeffnete eine Seite
voller Namen und Beobachtungen ueber ANDERE Menschen. Wer ihr glaubt
und drauftippt, sieht im ersten Moment das Gegenteil dessen, was
draufsteht.

Jetzt: eine Seite, ein Zweck.

  workspace/befinden.html + assets/js/befinden.js   die eigene Seite
  server/workspace-befinden.js                      Rhythmus + Verlauf
  entwicklung.html                                  nur noch ein Verweis

Was die Recherche zu Puls-Abfragen ergeben hat (Quellen im Kopf des
Moduls): kurz halten (fuenf bis fuenfzehn Fragen -- sechs bleiben
sechs), WIEDERHOLEN (zweiwoechentlich), und der VERLAUF ist die
Auskunft, nicht der Einzelwert. Bisher beantwortete man die sechs
Fragen einmal, und die Antwort stand fuer immer -- ein Befinden von
vor drei Monaten ist keine Auskunft mehr, sondern ein Andenken.

Der Verlauf gehoert ihr allein: eigene Tabelle, kein von_id, kein
anderer Weg im Haus liest sie. Im Protokoll steht nur, DASS eine
Runde war, nie was darin stand. Die Ampel zaehlt weiterhin nur den
aktuellen Stand und kennt keine Namen.

Zwei Dinge, die erst die Pruefung gefunden hat:

  - Wer die neue Seite oeffnete, ohne dass vorher jemand die
    Entwicklungsseite besucht hatte, bekam "no such table" und eine
    503. entwicklung_stand wird beim ersten Aufruf angelegt, nicht
    beim Start. Die Tabelle wird jetzt angefordert, nicht ein zweites
    Mal abgeschrieben.
  - pruef-entwicklung suchte die Kachel ueber ziel ===
    "entwicklung.html" und hat den gemeldeten Fehler damit
    mitgetragen. Sie sucht jetzt die eigene Seite -- und prueft
    zusaetzlich, dass keine Kachel mehr ersatzweise dorthin fuehrt.

Ausserdem weg: die tote Funktion meins() in entwicklung.js (sie
zeichnete in drei Stellen, die es nicht mehr gibt) und der Titel
"Wie geht's dir?", den diese Seite fuer einen Modi trug -- er
versprach etwas anderes als die Seite zeigt, also derselbe Fehler
wie an der Kachel.

  server/pruef-befinden.mjs   48 Pruefungen, 0 Fehler (Port 4419)
  pruef-entwicklung           43 (vorher 40), 0 Fehler
  pruef-nachwuchs 123, pruef-rechtetafel 19, pruef-rechte-umstellen 46,
  pruef-css-klassen -- alle 0 Fehler

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-15 13:45:56 +02:00

357 lines
14 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 Entwicklungskarte (11.09.2026)
Vier Knöpfe je Beobachtung, ein Feld für den Anlass, und oben die
Abweichungen zwischen zwei Augenpaaren. Mehr ist es nicht — und das
ist Absicht: „so einfach wie möglich, aber so professionell wie
möglich" (Filipe).
WAS HIER NICHT PASSIERT: rechnen. Es gibt keine Punktzahl, keinen
Durchschnitt, keine Ampel je Person. Eine Zahl über einem Menschen
wird gelesen, verglichen und weitererzählt — gebraucht wird ein
Gesprächsanlass.
UND KEINE RECHTEPRÜFUNG. Ob jemand Block 5 sehen darf, entscheidet
der Server: Er liefert ihn gar nicht erst aus. Was hier steht, ist
höchstens ein Abschnitt, der leer bleibt.
===================================================================== */
(() => {
const $ = (id) => document.getElementById(id);
const el = (t, k, text) => {
const n = document.createElement(t);
if (k) n.className = k;
if (text !== undefined) n.textContent = text;
return n;
};
const melde = (t) => { const f = $('fehler'); if (f) { f.textContent = t; f.hidden = !t; } };
const datum = (iso) => {
if (!iso) return '';
const d = new Date(iso);
return Number.isNaN(d.getTime()) ? '' : d.toLocaleDateString('de-DE',
{ day: 'numeric', month: 'short', year: 'numeric' });
};
async function hole(pfad, optionen = {}) {
const a = await fetch(pfad, { credentials: 'same-origin', ...optionen });
if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
if (a.status === 404) { location.assign('/workspace/start.html'); throw new Error('unbekannt'); }
return a;
}
let stufen = [];
let offen = null; // die gerade geöffnete Person
/* ---- Die vier Knöpfe ------------------------------------------------
Sie stehen immer in derselben Reihenfolge, auch wenn einer gesetzt
ist. Ein Knopfsatz, der sich umsortiert, sobald man ihn benutzt,
ist der sicherste Weg, beim zweiten Mal danebenzutippen. */
function knopfsatz(aktuell, waehle) {
const reihe = el('div', 'e-wahl');
for (const s of stufen) {
const b = el('button', 'e-wahl__knopf');
b.type = 'button';
b.dataset.stufe = s.schluessel;
b.setAttribute('aria-pressed', String(aktuell === s.schluessel));
b.title = s.text;
b.append(el('span', 'e-wahl__zeichen', s.zeichen));
b.append(el('span', 'e-wahl__name', s.name));
b.addEventListener('click', () => waehle(s.schluessel, b));
reihe.append(b);
}
return reihe;
}
/* ---- Die Ampel ------------------------------------------------------ */
async function ampel() {
const ziel = $('ampel');
let a;
try { a = await hole('/workspace/api/entwicklung/ampel'); } catch { return; }
if (!a.ok) return; // für einen Modi gibt es sie nicht
const r = await a.json();
ziel.textContent = '';
ziel.hidden = false;
ziel.dataset.lage = r.sagbar ? r.lage : 'still';
ziel.append(el('p', 'e-ampel__satz', r.sagbar ? r.text : r.grund));
if (r.sagbar) {
ziel.append(el('p', 'e-ampel__zusatz',
`${r.geantwortet} von ${r.team} im Team haben die sechs Fragen schon beantwortet. `
+ 'Namen stehen hier nicht – auch für dich nicht.'));
}
}
/* ---- Die Personen --------------------------------------------------- */
function personKarte(p, lage) {
const k = el('button', 'e-person');
k.type = 'button';
k.append(el('span', 'e-person__name', p.name));
k.append(el('span', 'e-person__rolle', p.rolle_name));
const zahlen = el('span', 'e-person__zahlen');
zahlen.append(el('span', 'e-person__zahl',
`${p.gesetzt} von ${lage.gesamt_fremd} angesehen`));
/* DIE ABWEICHUNG STEHT VORNE UND NICHT IM DETAIL. Sie ist das
Ergebnis dieser Seite: Wo zwei Leute dasselbe sehen, gibt es
nichts zu besprechen. */
if (p.abweichungen) {
const z = el('span', 'e-person__zahl e-person__zahl--wichtig',
`${p.abweichungen} × verschieden gesehen`);
z.title = 'Hier lohnt das Gespräch – nicht dort, wo ihr einig seid.';
zahlen.append(z);
}
if (p.veraltet) {
const z = el('span', 'e-person__zahl e-person__zahl--alt',
`${p.veraltet} älter als ${lage.verblasst_nach} Tage`);
z.title = 'Eine alte Einschätzung ist keine Einschätzung mehr.';
zahlen.append(z);
}
k.append(zahlen);
k.addEventListener('click', () => karte(p.id));
return k;
}
/* ---- Die Karte einer Person ----------------------------------------- */
async function karte(id) {
offen = id;
const ziel = $('karte');
ziel.hidden = false;
ziel.textContent = '';
ziel.append(el('p', 'leise', 'wird geladen …'));
ziel.scrollIntoView({ behavior: 'smooth', block: 'start' });
const a = await hole(`/workspace/api/entwicklung/person/${id}`);
if (!a.ok) { melde('Die Karte ließ sich nicht laden.'); return; }
const r = await a.json();
stufen = r.stufen;
ziel.textContent = '';
const kopf = el('div', 'e-karte__kopf');
kopf.append(el('h2', 'e-karte__name', r.person.name));
kopf.append(el('span', 'e-karte__rolle', r.person.rolle_name));
kopf.append(el('span', 'e-karte__stand', `${r.gesetzt} von ${r.gesamt} angesehen`));
/* Der Weg zum Notizbrett. Der Katalog ist das, was man regelmäßig
durchgeht; die Notiz das, was zwischendurch auffällt. Beides
nebeneinander, nicht statt. */
const brett = el('a', 'schritt e-karte__brett', 'Notizen zu dieser Person');
brett.href = 'bereich.html?b=entwicklung';
kopf.append(brett);
ziel.append(kopf);
/* ---- Abweichungen zuerst ------------------------------------------
Sie stehen oben, weil sie das Einzige sind, was diese Seite
liefert und eine Liste nicht: die Stelle, an der zwei Leute
dieselbe Person verschieden sehen. */
if (r.abweichungen.length) {
const box = el('div', 'e-abweichung');
box.append(el('h3', 'e-abweichung__titel',
`${r.abweichungen.length} × seht ihr das verschieden`));
box.append(el('p', 'e-abweichung__satz',
'Genau hier lohnt das Gespräch. Wo ihr einig seid, gibt es nichts zu besprechen.'));
const liste = el('ul', 'e-abweichung__liste');
for (const ab of r.abweichungen) {
const li = document.createElement('li');
li.append(el('span', 'e-abweichung__punkt', ab.titel));
const nn = (s) => stufen.find((x) => x.schluessel === s)?.name || s;
li.append(el('span', 'e-abweichung__gegen',
`du: ${nn(ab.meins)} · ${ab.von_name}: ${nn(ab.andere)}`));
liste.append(li);
}
box.append(liste);
ziel.append(box);
}
/* ---- Die Blöcke ---------------------------------------------------- */
for (const b of r.bloecke) {
const abschnitt = el('section', 'e-block');
abschnitt.append(el('h3', 'e-block__titel', b.name));
abschnitt.append(el('p', 'e-block__satz', b.text));
for (const pt of b.punkte) abschnitt.append(punktZeile(id, pt, r.erwartung));
ziel.append(abschnitt);
}
}
function punktZeile(personId, pt, erwartung) {
const z = el('article', 'e-punkt');
if (pt.meins) z.dataset.stufe = pt.meins.stufe;
if (pt.meins?.verblasst) z.dataset.verblasst = 'ja';
const kopf = el('div', 'e-punkt__kopf');
kopf.append(el('h4', 'e-punkt__titel', pt.titel));
const erw = erwartung.find((e) => e.schluessel === pt.stufe);
if (erw) {
const m = el('span', 'e-punkt__erwartung', erw.name);
m.dataset.erwartung = pt.stufe;
m.title = erw.text;
kopf.append(m);
}
z.append(kopf);
z.append(el('p', 'e-punkt__text', pt.text));
/* Das Anlassfeld. Es steht IMMER da, nicht erst nach dem Klick:
Ein Feld, das aufklappt, kostet einen zweiten Blick, und genau
an der Stelle hört man auf zu schreiben. */
const feld = el('textarea', 'e-punkt__anlass');
feld.rows = 2;
feld.value = pt.meins?.anlass || '';
feld.placeholder = 'Woran hast du das gemerkt? (bei „Da hakt es" nötig)';
z.append(knopfsatz(pt.meins?.stufe || null, async (stufe, knopf) => {
const reihe = knopf.parentElement;
for (const b of reihe.querySelectorAll('.e-wahl__knopf')) {
b.setAttribute('aria-pressed', String(b === knopf));
}
z.dataset.stufe = stufe;
const a = await hole(`/workspace/api/entwicklung/person/${personId}/${pt.schluessel}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ stufe, anlass: feld.value }),
});
if (!a.ok) {
const j = await a.json().catch(() => ({}));
melde(j.fehler || 'Ging nicht.');
feld.focus();
return;
}
melde('');
/* Neu laden, sobald der erste Klick auf einem Punkt sitzt: Erst
danach darf der Stand des anderen überhaupt ausgeliefert
werden, und dann soll er auch sichtbar werden. */
if (!pt.meins) await karte(personId);
}));
z.append(feld);
if (pt.andere?.length) {
for (const a of pt.andere) {
const nn = stufen.find((x) => x.schluessel === a.stufe);
const zeile = el('p', 'e-punkt__andere',
`${a.von_name}: ${nn?.name || a.stufe}${a.anlass ? ' – ' + a.anlass : ''}`);
zeile.dataset.stufe = a.stufe;
z.append(zeile);
}
} else if (pt.andere_offen) {
/* WARUM DA ETWAS STEHT UND NICHT NICHTS: „Noch niemand" und „du
darfst es noch nicht sehen" sähen sonst gleich aus — und das
erste ist eine Auskunft, das zweite eine Täuschung. */
z.append(el('p', 'e-punkt__verdeckt',
`${pt.andere_offen} andere Einschätzung liegt vor – sichtbar, sobald du selbst gesetzt hast.`));
}
if (pt.meins) {
z.append(el('p', 'e-punkt__wann',
`von dir am ${datum(pt.meins.am)}${pt.meins.verblasst ? ' – älter als 90 Tage' : ''}`));
}
return z;
}
/* ---- Deine eigene Karte -----------------------------------------------
WAS HIER STEHT UND WAS NICHT: die Antworten, nicht die Namen, und
nicht der Anlass. „2 × Läuft" oder „1 × Läuft, 1 × Wächst gerade".
Der Anlass ist für ein Gespräch aufgeschrieben, nicht zum Lesen
ohne den Menschen dazu — deshalb steht hier stattdessen der Satz,
dass man danach fragen soll. Ein Satz aus einer Notiz, aus dem
Zusammenhang gerissen, richtet mehr an, als er erklärt. */
async function meineKarte() {
let a;
try { a = await hole('/workspace/api/entwicklung/meine-karte'); } catch { return; }
if (!a.ok) return;
const r = await a.json();
if (!r.hat_karte) return; // DogFather hat keine
$('karte-block').hidden = false;
const satz = $('karte-satz');
const ziel = $('meine-karte');
ziel.textContent = '';
ziel.setAttribute('aria-busy', 'false');
if (!r.beurteiler) {
satz.textContent = 'Es gibt gerade niemanden, der sie führt. Sobald jemand '
+ 'da ist, steht hier etwas.';
return;
}
if (!r.fertig) {
satz.textContent = `Noch nichts fertig. Eine Beobachtung erscheint hier erst, `
+ `wenn alle ${r.beurteiler} sie gesetzt haben – eine halbe Karte ist `
+ `schlimmer als keine.`;
return;
}
satz.textContent = `${r.fertig} von ${r.gesamt} Beobachtungen sind durch. `
+ 'Du siehst die Antworten, nicht wer was gesetzt hat – und nicht, was '
+ 'danebengeschrieben steht. Das gehört ins Gespräch: frag danach.';
for (const b of r.bloecke) {
const fertige = b.punkte.filter((pt) => !pt.offen);
if (!fertige.length) continue;
const abschnitt = el('section', 'e-block');
abschnitt.append(el('h3', 'e-block__titel', b.name));
for (const pt of fertige) {
const zeile = el('article', 'e-punkt e-punkt--meine');
if (pt.einig) zeile.dataset.stufe = pt.antworten[0].stufe;
zeile.append(el('h4', 'e-punkt__titel', pt.titel));
const antw = el('div', 'e-meine__antworten');
for (const x of pt.antworten) {
const chip = el('span', 'e-meine__antwort', `${x.anzahl} × ${x.name}`);
chip.dataset.stufe = x.stufe;
antw.append(chip);
}
if (!pt.einig) {
const hin = el('span', 'e-meine__uneinig', 'darüber seid ihr euch nicht einig');
hin.title = 'Zwei Leute haben dasselbe verschieden gesehen. Das ist normal – '
+ 'und ein guter Anlass, danach zu fragen.';
antw.append(hin);
}
zeile.append(antw);
abschnitt.append(zeile);
}
ziel.append(abschnitt);
}
}
/* ---- Start ------------------------------------------------------------ */
(async () => {
try {
const a = await hole('/workspace/api/entwicklung/lage');
if (!a.ok) { melde('Ging nicht.'); return; }
const lage = await a.json();
stufen = lage.stufen;
if (lage.leitung) {
$('personen-block').hidden = false;
const ziel = $('personen');
ziel.textContent = '';
ziel.setAttribute('aria-busy', 'false');
if (!lage.personen.length) {
ziel.append(el('p', 'leise',
'Noch niemand im Team. Zugänge legst du in „Personen & Zugänge" an.'));
}
for (const p of lage.personen) ziel.append(personKarte(p, lage));
await ampel();
} else {
/* Für einen Modi ist DIESE Seite seine eigene Karte — das,
was die Leitung über ihn notiert hat, und was er selbst
dazu sagt. Nicht „Entwicklung" als Überschrift: Das liest
sich wie eine Beurteilung, und darum geht es hier nicht.
Bis zum 15.09.2026 stand hier „Wie geht's dir?" — damals
bestand die Seite für ihn aus den sechs Fragen. Die haben
jetzt ihre eigene Seite, also stimmte der Titel nicht mehr.
Ein Titel, der etwas anderes verspricht als die Seite zeigt,
ist derselbe Fehler wie der an der Kachel. */
$('titel').textContent = 'Deine Entwicklung';
$('unterzeile').textContent =
'Was im Team über deine Arbeit notiert ist – und was du selbst dazu sagst. '
+ 'Keine Noten, keine Punktzahl.';
}
await meineKarte();
/* Die sechs Fragen stehen seit dem 15.09.2026 auf einer eigenen
Seite. Hier bleibt nur der Verweis -- er wird fuer alle
gezeigt, die dort ueberhaupt etwas zu beantworten haben. */
if ($('meins-block')) $('meins-block').hidden = false;
} catch { /* umgeleitet */ }
})();
})();