Ausgeloest von pruef-modi-wortleck, die seit Tagen rot war: "24
Fundstellen -- der verborgene Zugang waere damit auffindbar." Die
erste Frage war, ob sie recht hat. Gemessen: ja, und schlimmer als
gedacht.
WER DIESE DATEIEN BEKOMMT: Nicht nur Kollegen. `curl` ohne jeden Keks
auf workspace.dogfather-universe.com liefert die Skripte und
Stilvorlagen des Arbeitsplatzes mit HTTP 200 -- fuer jeden im Netz
lesbar.
DARUNTER crew-haus.css, 27 806 Byte, das ganze Farbhaus von Team Dogi
samt seiner Kommentare. In deren Kopf stand seit dem 10.09. woertlich
"Diese Datei wird NUR auf crew.dogfather-universe.com ausgeliefert."
Das war eine Behauptung, keine Tatsache: Die Weiche biegt `haus.css`
auf diesen Namen um, wer den Namen direkt nannte, ging an ihr vorbei.
Ein Kommentar, der eine Absicht beschreibt, erfuellt sie nicht.
Zwei Stellen im Haus haben sich damit widersprochen -- die Datei sagte
"nur auf crew.", pruef-crew-adresse verlangte ausdruecklich das
Gegenteil ("sie ist kein Geheimnis"). Durchgesetzt war die offene
Fassung. Filipe hat am 21.09. entschieden: zusperren. Die alte
Begruendung dagegen war die Sorge, eine Sperre koenne die Pruefung
blind machen -- die ist jetzt beantwortet statt vermieden: Jeder
Eintrag der Tafel wird in BEIDE Richtungen gemessen, und dass die
Umbiegung von `haus.css` auf crew. weiterhin greift, ebenfalls.
DIE ADRESSE WAR DAS EIGENTLICHE GEHEIMNIS, nicht das Wort. Die
Rollennamen stehen auf der Zugangswand von Team Dogi ohnehin offen --
so gewollt. WO sie liegt, stand dagegen im Klartext in gate.css,
gate.js und crew-haus.css, alle drei oeffentlich abrufbar. Jetzt steht
sie in keiner ausgelieferten Datei mehr.
DIE PRUEFUNG SELBST HATTE DREI MAENGEL:
1. Sie kannte die Mehrzahl nicht. `\bmodi\b` fand "ein Modi", nicht
"die Modis" -- 23 weitere Stellen, darunter der Satz "Fuer die
rechte Hand und die Modis gibt es dort den stillen Zugang". Also
genau die Auskunft, die sie verhindern soll, in der Form, die sie
nicht sah.
2. Ihre Ausnahmeliste war abgeschrieben. Sie nannte eine Datei; die
zweite, die der Server auf crew. begrenzt, haette sie nie
erfahren. Ausgenommen ist jetzt genau das, was der Server auch
durchsetzt (GEHOERT_ZU_ADRESSE) -- kein Muster, sondern die
Durchsetzung selbst.
3. Sie mahnte 45 Kommentare an. Filipes Entscheidung: in Kommentaren
ja, in Code und sichtbaren Texten nein. Rot, das immer da ist,
wird ueberlesen -- und dann faengt sie auch den echten Fall nicht.
Dafuer neu: helfer-ohne-kommentar.mjs, ein Zerleger, der Kommentare
zeichengenau durch Leerzeichen ersetzt, ohne Zeichenketten,
Vorlagen oder Suchmuster zu beschaedigen. KEIN Suchausdruck -- genau
daran ist am 20.09. die Portumstellung gescheitert, mit gueltigem
JavaScript ohne das Feld `port`. Dreizehn Proben in beide Richtungen
beweisen, dass er weder zu viel noch zu wenig wegnimmt; ein Zerleger,
der zu viel nimmt, macht die ganze Pruefung STILL gruen.
Von 45 blieben damit drei echte, alle behoben:
- daten.modis -> daten.leute (Feldname in der Auskunft, also Code)
- zwei Saetze, die Menschen lesen, auf "die Moderation" umgestellt --
fuer jemanden, der neu im Treff ist, sogar klarer
Nebenbei, beim selben Durchgang gefunden und geschlossen:
- teamlage.js fiel auf einen Rollennamen zurueck. Nachgemessen: Die
Stilvorlage kennt zu dieser Karte gar keine solche Regel -- der
Rueckfall hat nie etwas bewirkt und nur das Wort ausgeliefert.
- werdegang.js fuehrte die Saetze ueber den Rollengruppen selbst,
mit den Rollennamen als Schluessel. Der Kommentar daneben
behauptete sogar, sie seien "abgeleitet, nicht abgeschrieben".
Sie kommen jetzt vom Server, und pruef-werdegang misst, dass
jede Gruppe ihren eigenen behaelt.
pruef-modi-wortleck 8/0 (war 5, davon 1 rot)
pruef-crew-adresse 144/0 (war 135)
pruef-werdegang 98 statt 95 Pruefungen, dieselben 3 alten Fehler
pruef-team-ampel 32/0, pruef-team-stufen 28/0, pruef-hilfe 84/0
Co-Authored-By: Claude Opus 5 <[email protected]>
585 lines
25 KiB
JavaScript
585 lines
25 KiB
JavaScript
/* =====================================================================
|
||
ENTWICKLUNG — die Auswertung je Person (19.09.2026)
|
||
|
||
Zwei Gesichter, und welches man sieht, entscheidet der Server:
|
||
Wer das Team führt, bekommt die Karten; wer dazugehört, bekommt
|
||
ausschließlich die Bewegung in der eigenen.
|
||
|
||
---------------------------------------------------------------------
|
||
DIE DIAGRAMME SIND SELBST GEZEICHNET
|
||
|
||
Kein Diagrammpaket. Zwei Formen, beide aus wenigen Zeilen:
|
||
|
||
BEWEGUNG Balken um eine Nulllinie, nach oben „nach vorn“, nach
|
||
unten „zurück“. Sechs Monate, mehr passt auf ein Handy
|
||
nicht und sagt auch nichts.
|
||
STREIFEN Ein Raster: Zeile = Punkt, Spalte = Monat, Zelle = die
|
||
Stufe, die damals galt. Dieselbe Bauform wie der
|
||
Befinden-Verlauf — wer die eine kennt, liest die andere.
|
||
|
||
Ein Paket dafür wären 60 kB über eine Leitung, auf der jemand mit dem
|
||
Handy im Stream steht. Und es brächte seine eigenen Farben mit, die
|
||
dann neben den Hausfarben stünden.
|
||
|
||
---------------------------------------------------------------------
|
||
DIE FARBE IST NIE DIE AUSKUNFT
|
||
|
||
Jede Zelle trägt das Zeichen ihrer Stufe als Text (✓ ↗ ! –), jeder
|
||
Balken seine Zahl. Wer Farben schlecht unterscheidet, liest genau
|
||
dasselbe. Das ist im Haus seit dem Befinden-Verlauf so.
|
||
|
||
Und es gibt hier kein Rot. „Da hakt es“ ist eine Beobachtung, kein
|
||
Alarm; ein Rückschritt heißt oft nur, dass jemand genauer hingesehen
|
||
hat als beim letzten Mal.
|
||
===================================================================== */
|
||
(() => {
|
||
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; } };
|
||
|
||
async function hole(pfad) {
|
||
const a = await fetch(pfad, { credentials: 'same-origin' });
|
||
if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
|
||
return a;
|
||
}
|
||
|
||
/* Die vier Stufen, wie der Server sie schickt — Zeichen inklusive.
|
||
Eine eigene Liste hier wäre die, die beim nächsten Nachschärfen
|
||
auseinanderläuft. */
|
||
let stufen = [];
|
||
const zeichenVon = (s) => stufen.find((x) => x.schluessel === s)?.zeichen || '·';
|
||
const namenVon = (s) => stufen.find((x) => x.schluessel === s)?.name || s;
|
||
|
||
const MONATSNAME = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun',
|
||
'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'];
|
||
function monatKurz(m) {
|
||
const [j, mm] = String(m).split('-').map(Number);
|
||
if (!j || !mm) return m;
|
||
return `${MONATSNAME[mm - 1]}${mm === 1 ? ' ' + String(j).slice(2) : ''}`;
|
||
}
|
||
function datumKurz(iso) {
|
||
const t = Date.parse(iso || '');
|
||
if (!Number.isFinite(t)) return '';
|
||
return new Date(t).toLocaleDateString('de-DE',
|
||
{ day: '2-digit', month: '2-digit', year: '2-digit' });
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIAGRAMM 1 — BEWEGUNG
|
||
|
||
ZWEI RICHTUNGEN UM EINE NULLLINIE. „Nach vorn“ und „zurück“ sind
|
||
dieselbe Größe mit verschiedenem Vorzeichen; zwei getrennte
|
||
Diagramme würden dazu verleiten, sie wie zwei Themen zu lesen.
|
||
|
||
EINE ACHSE, EIN MASSSTAB. Der höchste Balken bestimmt ihn, und er
|
||
gilt für oben und unten gleichermaßen — sonst sähe ein einzelner
|
||
Rückschritt so groß aus wie fünf Fortschritte.
|
||
|
||
KEINE ZAHL AN JEDEM BALKEN. Nur dort, wo etwas steht. Eine Null an
|
||
jedem leeren Monat wäre Lärm; die leere Spalte sagt dasselbe.
|
||
===================================================================== */
|
||
function bewegungBauen(bewegung, spalten) {
|
||
const wurzel = el('div', 'w-bewegung');
|
||
|
||
const hoechst = Math.max(1, ...bewegung.map(
|
||
(m) => Math.max(m.vor + m.geklaert + m.neu, m.zurueck + m.offen)));
|
||
|
||
const gitter = el('div', 'w-bewegung__gitter');
|
||
gitter.style.setProperty('--spalten', String(spalten.length));
|
||
|
||
for (const m of bewegung) {
|
||
const spalte = el('div', 'w-bewegung__spalte');
|
||
|
||
const oben = el('div', 'w-bewegung__halb w-bewegung__halb--oben');
|
||
const obenWert = m.vor + m.geklaert + m.neu;
|
||
if (obenWert) {
|
||
const b = el('div', 'w-bewegung__balken');
|
||
b.dataset.richtung = 'vor';
|
||
b.style.height = `${Math.round((obenWert / hoechst) * 100)}%`;
|
||
b.title = [
|
||
m.vor ? `${m.vor}× nach vorn` : null,
|
||
m.geklaert ? `${m.geklaert}× geklärt` : null,
|
||
m.neu ? `${m.neu}× zum ersten Mal` : null,
|
||
].filter(Boolean).join(', ');
|
||
oben.append(el('span', 'w-bewegung__zahl', String(obenWert)), b);
|
||
}
|
||
spalte.append(oben);
|
||
|
||
spalte.append(el('div', 'w-bewegung__null'));
|
||
|
||
const unten = el('div', 'w-bewegung__halb w-bewegung__halb--unten');
|
||
const untenWert = m.zurueck + m.offen;
|
||
if (untenWert) {
|
||
const b = el('div', 'w-bewegung__balken');
|
||
b.dataset.richtung = 'zurueck';
|
||
b.style.height = `${Math.round((untenWert / hoechst) * 100)}%`;
|
||
b.title = [
|
||
m.zurueck ? `${m.zurueck}× zurückgesetzt` : null,
|
||
m.offen ? `${m.offen}× auf „Kann ich nicht sagen“` : null,
|
||
].filter(Boolean).join(', ');
|
||
unten.append(b, el('span', 'w-bewegung__zahl', String(untenWert)));
|
||
}
|
||
spalte.append(unten);
|
||
|
||
spalte.append(el('div', 'w-bewegung__monat', monatKurz(m.monat)));
|
||
gitter.append(spalte);
|
||
}
|
||
wurzel.append(gitter);
|
||
|
||
/* DIE LEGENDE IST PFLICHT, nicht Zierde: Ohne sie trägt allein die
|
||
Farbe die Bedeutung, und das ist genau die Stelle, an der ein
|
||
Diagramm für manche Leute aufhört zu funktionieren. */
|
||
const legende = el('div', 'w-legende');
|
||
for (const [richtung, text] of [
|
||
['vor', 'nach vorn · geklärt · zum ersten Mal'],
|
||
['zurueck', 'zurückgesetzt · wieder offen'],
|
||
]) {
|
||
const p = el('span', 'w-legende__punkt');
|
||
const fleck = el('span', 'w-legende__fleck');
|
||
fleck.dataset.richtung = richtung;
|
||
p.append(fleck, el('span', 'w-legende__text', text));
|
||
legende.append(p);
|
||
}
|
||
wurzel.append(legende);
|
||
|
||
/* DIE ZAHLEN IN WORTEN (ergänzt 19.09.2026).
|
||
|
||
Über dem Balken steht „4". Was die 4 ausmacht, stand nur im
|
||
`title` — also beim Mausüberfahren, also am Handy nie. Und genau
|
||
dort steht jemand damit: im Stream, zwischen zwei Szenen.
|
||
|
||
Nur Monate mit Inhalt, damit aus sechs Zeilen nicht fünf leere
|
||
werden. */
|
||
const worte = el('div', 'w-bewegung__worte');
|
||
for (const m of bewegung) {
|
||
const teile = [
|
||
m.vor ? `${m.vor}× nach vorn` : null,
|
||
m.geklaert ? `${m.geklaert}× geklärt` : null,
|
||
m.neu ? `${m.neu}× zum ersten Mal` : null,
|
||
m.zurueck ? `${m.zurueck}× zurückgesetzt` : null,
|
||
m.offen ? `${m.offen}× wieder offen` : null,
|
||
].filter(Boolean);
|
||
if (!teile.length) continue;
|
||
const zeile = el('p', 'w-bewegung__wortzeile');
|
||
zeile.append(el('strong', 'w-bewegung__wortmonat', monatKurz(m.monat)));
|
||
zeile.append(el('span', 'w-bewegung__worttext', teile.join(' · ')));
|
||
worte.append(zeile);
|
||
}
|
||
if (worte.childElementCount) wurzel.append(worte);
|
||
return wurzel;
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIAGRAMM 2 — DER STREIFEN
|
||
===================================================================== */
|
||
function streifenBauen(streifen, spalten) {
|
||
const gitter = el('div', 'w-streifen__gitter');
|
||
/* DIE SPALTENZAHL KOMMT AUS DEN DATEN (19.09.2026). Vorher rechnete
|
||
das CSS sie aus der Breite (`auto-fit`) -- bei 390 px passten
|
||
sechs, und alles darueber hinaus fiel in eine zweite,
|
||
unbeschriftete Zeile. Dieselbe Loesung wie beim
|
||
Bewegungsdiagramm nebenan. */
|
||
gitter.style.setProperty('--monate', String(spalten.length));
|
||
|
||
const kopf = el('div', 'w-streifen__zeile w-streifen__zeile--kopf');
|
||
kopf.append(el('span', 'w-streifen__punkt-name', ''));
|
||
for (const m of spalten) kopf.append(el('span', 'w-streifen__monat', monatKurz(m)));
|
||
gitter.append(kopf);
|
||
|
||
for (const z of streifen) {
|
||
const zeile = el('div', 'w-streifen__zeile');
|
||
zeile.append(el('span', 'w-streifen__punkt-name', z.titel));
|
||
for (const c of z.zellen) {
|
||
/* LEER IST NICHT „KANN ICH NICHT SAGEN“ (getrennt 19.09.2026).
|
||
|
||
Beim Durchgehen aus Benutzersicht aufgefallen: Hier stand für
|
||
„nie etwas gesetzt“ ein Mittelpunkt `·` — und „Kann ich nicht
|
||
sagen“ trägt im Katalog den Halbgeviertstrich `–`. Zwei kleine
|
||
waagerechte Striche in Nachbarzellen, und der Unterschied
|
||
zwischen ihnen ist genau der, den dieses Haus sonst sehr ernst
|
||
nimmt: „niemand hat hingesehen“ gegen „jemand hat hingesehen
|
||
und konnte es nicht sagen“.
|
||
|
||
Leer ist jetzt WIRKLICH leer — kein Zeichen, dafür ein
|
||
gestrichelter Rahmen. Man sieht das Fehlen, statt es zu lesen. */
|
||
const zelle = el('span', 'w-streifen__zelle',
|
||
c.stufe ? zeichenVon(c.stufe) : '');
|
||
if (c.stufe) {
|
||
zelle.dataset.stufe = c.stufe;
|
||
if (c.alt) zelle.dataset.alt = 'ja';
|
||
zelle.title = `${monatKurz(c.monat)}: ${namenVon(c.stufe)}`
|
||
+ (c.seit ? ` (gesetzt am ${datumKurz(c.seit)})` : '')
|
||
+ (c.alt ? ' – älter als drei Monate' : '');
|
||
} else {
|
||
zelle.dataset.stufe = 'leer';
|
||
zelle.title = `${monatKurz(c.monat)}: noch keine Einschätzung`;
|
||
}
|
||
zeile.append(zelle);
|
||
}
|
||
gitter.append(zeile);
|
||
}
|
||
|
||
/* DIE ZEICHENERKLÄRUNG — und sie ist der Grund, warum die Zeichen
|
||
überhaupt etwas taugen (ergänzt 19.09.2026).
|
||
|
||
Im Kopf dieser Datei steht: „Jede Zelle trägt das Zeichen ihrer
|
||
Stufe als Text. Wer Farben schlecht unterscheidet, liest genau
|
||
dasselbe." Das stimmt nur, wenn irgendwo steht, WAS die Zeichen
|
||
heißen. Tat es nicht — die Bedeutung steckte ausschließlich im
|
||
Mausüberfahren, und auf einem Handy gibt es das nicht.
|
||
|
||
Gebaut aus `stufen` vom Server, nicht abgeschrieben: Wer dort ein
|
||
Zeichen ändert, ändert die Erklärung mit. */
|
||
const legende = el('div', 'w-legende w-legende--streifen');
|
||
for (const st of stufen) {
|
||
const p = el('span', 'w-legende__punkt');
|
||
const z = el('span', 'w-legende__zeichen', st.zeichen);
|
||
z.dataset.stufe = st.schluessel;
|
||
p.append(z, el('span', 'w-legende__text', st.name));
|
||
legende.append(p);
|
||
}
|
||
const leerP = el('span', 'w-legende__punkt');
|
||
const leerZ = el('span', 'w-legende__zeichen', '');
|
||
leerZ.dataset.stufe = 'leer';
|
||
leerP.append(leerZ, el('span', 'w-legende__text', 'noch nichts gesetzt'));
|
||
legende.append(leerP);
|
||
|
||
const huelle = el('div');
|
||
huelle.append(gitter, legende);
|
||
return huelle;
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIE TEXTE
|
||
===================================================================== */
|
||
function texteBauen(texte) {
|
||
const wurzel = el('div', 'w-texte');
|
||
for (const t of texte) {
|
||
const kasten = el('section', 'w-text');
|
||
kasten.dataset.art = t.art;
|
||
kasten.append(el('h4', 'w-text__titel', t.titel));
|
||
kasten.append(el('p', 'w-text__satz', t.satz));
|
||
if (t.punkte?.length) {
|
||
const liste = el('ul', 'w-text__liste');
|
||
for (const p of t.punkte) {
|
||
const zeile = el('li', 'w-text__zeile');
|
||
zeile.append(el('span', 'w-text__punkt', p.titel));
|
||
const rechts = el('span', 'w-text__rechts');
|
||
if (p.von && p.zu) {
|
||
rechts.append(el('span', 'w-text__stufe', namenVon(p.von)));
|
||
rechts.append(el('span', 'w-text__pfeil', '→'));
|
||
}
|
||
if (p.zu) {
|
||
const s = el('span', 'w-text__stufe', namenVon(p.zu));
|
||
s.dataset.stufe = p.zu;
|
||
rechts.append(s);
|
||
}
|
||
if (p.erwartung) rechts.append(el('span', 'w-text__stufe', p.erwartung));
|
||
if (p.tage != null) rechts.append(el('span', 'w-text__tage', `seit ${p.tage} T.`));
|
||
else if (p.am) rechts.append(el('span', 'w-text__tage', datumKurz(p.am)));
|
||
zeile.append(rechts);
|
||
liste.append(zeile);
|
||
}
|
||
kasten.append(liste);
|
||
}
|
||
wurzel.append(kasten);
|
||
}
|
||
return wurzel;
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIE KARTE EINER PERSON
|
||
===================================================================== */
|
||
async function karteZeigen(id) {
|
||
const a = await hole(`/workspace/api/werdegang/person/${id}`);
|
||
if (!a.ok) { melde('Diese Karte lässt sich gerade nicht öffnen.'); return; }
|
||
const d = await a.json();
|
||
stufen = d.stufen || [];
|
||
|
||
$('liste-block').hidden = true;
|
||
$('mich-block').hidden = true;
|
||
$('karte-block').hidden = false;
|
||
$('karte-name').textContent = `${d.person.name} · ${d.person.rolle_name}`;
|
||
$('karte-satz').textContent = d.satz;
|
||
|
||
/* ==== DER DRITTE AUSGANG ========================================
|
||
|
||
Nicht „in Ordnung“ und nicht „nicht in Ordnung“, sondern:
|
||
KONNTE NOCH NICHT NACHSEHEN — mit Grund. Ein Diagramm über
|
||
einen einzigen Zeitpunkt wäre kein Diagramm, sondern ein
|
||
Balken, und er sähe aus wie eine Aussage.
|
||
============================================================= */
|
||
const bBlock = $('bewegung-block'), sBlock = $('streifen-block');
|
||
if (!d.verlauf_moeglich) {
|
||
bBlock.hidden = false; sBlock.hidden = true;
|
||
$('bewegung').replaceChildren();
|
||
$('bewegung-satz').textContent = d.gesetzt === 0
|
||
? 'Zu dieser Person ist noch nichts eingetragen. Sobald die erste '
|
||
+ 'Einschätzung steht, beginnt hier der Verlauf.'
|
||
: `Es gibt bisher ${d.monate_mit_inhalt === 1 ? 'einen Monat' : d.monate_mit_inhalt + ' Monate'} `
|
||
+ `mit Einschätzungen. Ein Verlauf braucht mindestens ${d.verlauf_ab} – `
|
||
+ 'vorher stünde hier eine Linie, die nichts bedeutet.';
|
||
} else {
|
||
bBlock.hidden = false; sBlock.hidden = false;
|
||
$('bewegung-satz').textContent =
|
||
`Die letzten ${d.spalten.length} Monate. Nach oben: weitergekommen, `
|
||
+ 'geklärt oder zum ersten Mal beantwortet. Nach unten: zurückgesetzt '
|
||
+ 'oder wieder offen. Beides ist eine Auskunft, keines ist eine Note.';
|
||
$('bewegung').replaceChildren(bewegungBauen(d.bewegung, d.spalten));
|
||
|
||
$('streifen-satz').textContent =
|
||
'Was in einem Monat galt, gilt auch im nächsten, solange niemand etwas '
|
||
+ `ändert – deshalb sind die Zellen durchgezogen. Blasse Zellen sind älter `
|
||
+ `als ${d.verblasst_nach_tagen} Tage und zählen nicht mehr mit.`;
|
||
$('streifen').replaceChildren(streifenBauen(d.streifen, d.spalten));
|
||
}
|
||
|
||
const tBlock = $('texte-block');
|
||
if (d.texte?.length) {
|
||
tBlock.hidden = false;
|
||
$('texte').replaceChildren(texteBauen(d.texte));
|
||
} else {
|
||
tBlock.hidden = true;
|
||
}
|
||
|
||
/* Die Adresse merkt sich, welche Karte offen ist — damit „Zurück“
|
||
auf die Liste führt und nicht aus der Seite heraus. */
|
||
history.replaceState(null, '', `?p=${id}`);
|
||
window.scrollTo({ top: 0, behavior: 'instant' });
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIE LISTE
|
||
|
||
KEINE SORTIERUNG NACH AKTIVITÄT. Eine Liste, die die mit der
|
||
meisten Bewegung nach oben stellt, ist eine Rangliste mit einem
|
||
anderen Namen. Der Server sortiert nach Namen, und dabei bleibt es.
|
||
===================================================================== */
|
||
/* DER SATZ ÜBER EINER GRUPPE KOMMT VOM SERVER (21.09.2026).
|
||
|
||
Hier stand er als Liste im Quelltext -- mit den Rollennamen als
|
||
Schlüssel. Der Kommentar daneben behauptete sogar, er sei
|
||
"abgeleitet, nicht abgeschrieben"; das stimmte für den NAMEN
|
||
daneben, nicht für diese Liste.
|
||
|
||
ZWEI GRÜNDE, WARUM ER JETZT VOM SERVER KOMMT:
|
||
|
||
(1) Diese Datei lädt JEDER herunter, der den Arbeitsplatz öffnet
|
||
-- auch jeder Manager, Scout und Creator auf
|
||
der Agenturadresse, und zwar OHNE Anmeldung. Rollennamen der
|
||
Community gehören dort nicht hin; darüber wacht eine eigene
|
||
Prüfung im Serverordner. Dieselbe Entscheidung wie bei
|
||
ROLLENTEXT in start.js.
|
||
|
||
(2) Es wäre die zweite Liste gewesen. Kommt eine Rolle dazu,
|
||
schickt der Server ihren Namen von selbst mit -- ihr Satz
|
||
hätte hier nachgetragen werden müssen, und genau so entstehen
|
||
Gruppen mit Überschrift und ohne Erklärung. */
|
||
|
||
function listeBauen(d) {
|
||
const wurzel = el('div', 'w-liste');
|
||
|
||
/* ---- NACH ROLLE GRUPPIERT (19.09.2026) -------------------------
|
||
|
||
Filipe: „die reihenfolge der listen soll immer angepasst sein
|
||
... die rechte hand rolle immer zuerst und dan die modis. die
|
||
sollen auch schoen getrennt sein und verschieden aussehen also
|
||
die rechte hand viel spezieller."
|
||
|
||
Die REIHENFOLGE macht der Server (ROLLEN_SORTIERUNG) -- hier
|
||
wird nur gruppiert, wo sie wechselt. Eine eigene Sortierung im
|
||
Browser waere die zweite Wahrheit, und die zweite ist die, die
|
||
beim naechsten Umbau abweicht.
|
||
|
||
DAS IST KEINE RANGLISTE. Eine Sortierung nach Rolle sagt nichts
|
||
darueber, wie gut jemand ist -- nur, welche Aufgabe er hat.
|
||
Deshalb steht ueber jeder Gruppe ein Satz, der genau das
|
||
erklaert, und keine Zahl. */
|
||
let letzteRolle = null;
|
||
let gitter = null;
|
||
for (const p of d.liste) {
|
||
if (p.rolle !== letzteRolle) {
|
||
letzteRolle = p.rolle;
|
||
const kopf = el('div', 'w-gruppe');
|
||
kopf.dataset.rolle = p.rolle;
|
||
kopf.append(el('h3', 'w-gruppe__name', p.rolle_name));
|
||
if (p.rolle_satz) {
|
||
kopf.append(el('p', 'w-gruppe__satz', p.rolle_satz));
|
||
}
|
||
wurzel.append(kopf);
|
||
gitter = el('div', 'w-liste__gitter');
|
||
gitter.dataset.rolle = p.rolle;
|
||
wurzel.append(gitter);
|
||
}
|
||
const karte = el('button', 'w-karte');
|
||
karte.type = 'button';
|
||
/* Die Rolle haengt an der Karte, nicht an einer Klasse: So kann
|
||
das CSS sie einfaerben, ohne dass hier eine zweite Liste von
|
||
Rollennamen entsteht. */
|
||
karte.dataset.rolle = p.rolle;
|
||
karte.append(el('span', 'w-karte__name', p.name));
|
||
karte.append(el('span', 'w-karte__rolle', p.rolle_name));
|
||
|
||
const zeilen = el('span', 'w-karte__zeilen');
|
||
zeilen.append(el('span', 'w-karte__zeile',
|
||
p.gesetzt === 0
|
||
? 'Noch nichts eingetragen'
|
||
: `${p.gesetzt} von ${p.gesamt} Punkten beantwortet`));
|
||
zeilen.append(el('span', 'w-karte__zeile',
|
||
p.bewegt === 0
|
||
? `In ${d.monate} Monaten keine Änderung`
|
||
: `${p.bewegt} Änderung${p.bewegt === 1 ? '' : 'en'} in ${d.monate} Monaten`
|
||
+ (p.vor ? ` · ${p.vor} nach vorn` : '')));
|
||
if (p.zuletzt_tage != null) {
|
||
const z = el('span', 'w-karte__zeile',
|
||
p.zuletzt_tage === 0 ? 'Zuletzt heute angesehen'
|
||
: `Zuletzt vor ${p.zuletzt_tage} Tag${p.zuletzt_tage === 1 ? '' : 'en'} angesehen`);
|
||
/* DIE EINZIGE FARBE IN DER LISTE, und sie sagt etwas über UNS:
|
||
Wer seit über drei Monaten nicht hingesehen hat, hat keine
|
||
Auskunft über diesen Menschen, sondern über sich. */
|
||
if (p.zuletzt_tage >= 90) z.dataset.alt = 'ja';
|
||
zeilen.append(z);
|
||
}
|
||
karte.append(zeilen);
|
||
karte.addEventListener('click', () => karteZeigen(p.id).catch(() => {}));
|
||
(gitter || wurzel).append(karte);
|
||
}
|
||
return wurzel;
|
||
}
|
||
|
||
async function listeZeigen() {
|
||
const a = await hole('/workspace/api/werdegang/liste');
|
||
if (a.status === 404) return false; /* kein Leitungszugang */
|
||
if (!a.ok) { melde('Die Karten lassen sich gerade nicht laden.'); return true; }
|
||
const d = await a.json();
|
||
|
||
$('karte-block').hidden = true;
|
||
$('mich-block').hidden = true;
|
||
$('liste-block').hidden = false;
|
||
$('liste').removeAttribute('aria-busy');
|
||
$('liste-hinweis').textContent = d.hinweis;
|
||
|
||
if (!d.liste.length) {
|
||
$('liste').replaceChildren(el('p', 'leise',
|
||
'Es gibt noch niemanden mit einer Karte. Sobald jemand ins Team kommt, '
|
||
+ 'steht er hier.'));
|
||
} else {
|
||
$('liste').replaceChildren(listeBauen(d));
|
||
}
|
||
history.replaceState(null, '', location.pathname);
|
||
return true;
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIE EIGENE SICHT
|
||
|
||
OHNE NAMEN, OHNE ANLASS, OHNE STREIFEN. Sie sieht, DASS sich etwas
|
||
bewegt hat und wann — nicht, wer was gesetzt hat und was
|
||
danebensteht. Der Anlass ist für ein Gespräch aufgeschrieben, nicht
|
||
zum Lesen ohne den Menschen dazu.
|
||
===================================================================== */
|
||
async function michZeigen() {
|
||
const a = await hole('/workspace/api/werdegang/mich');
|
||
if (!a.ok) { melde('Das lässt sich gerade nicht laden.'); return; }
|
||
const d = await a.json();
|
||
|
||
$('liste-block').hidden = true;
|
||
$('karte-block').hidden = true;
|
||
$('mich-block').hidden = false;
|
||
$('titel').textContent = 'Deine Entwicklung';
|
||
$('unterzeile').textContent =
|
||
'Was sich über die Zeit bewegt hat. Keine Note, keine Punktzahl.';
|
||
|
||
if (!d.hat_karte) {
|
||
$('mich-satz').textContent =
|
||
'Für deine Rolle wird keine Entwicklungskarte geführt. Das ist keine '
|
||
+ 'Lücke – es gibt schlicht nichts, was hier stehen müsste.';
|
||
$('mich-hinweis').textContent = '';
|
||
return;
|
||
}
|
||
stufen = d.stufen || stufen;
|
||
$('mich-satz').textContent = d.satz;
|
||
$('mich-hinweis').textContent = d.hinweis;
|
||
/* DREI DINGE, DIE HIER GEFEHLT HABEN (19.09.2026).
|
||
|
||
Beim Durchgehen aus der Sicht dessen, dem diese Seite gehört,
|
||
fiel auf: Er bekam Balken ohne die Erklärung, die die Leitung
|
||
daneben bekommt („Nach oben: weitergekommen"). Er bekam eine
|
||
Aufforderung ohne Adressaten („Frag danach" — wen?). Und wenn
|
||
sich nichts bewegt hatte, bekam er ein leeres Element, also
|
||
nichts: eine Seite, die einlädt und dann schweigt.
|
||
|
||
Alle drei sind hier behoben. Es ist derselbe Gedanke wie überall
|
||
im Haus: Der leere Zustand ist kein Sonderfall, er ist der
|
||
häufigste Fall — vor allem am Anfang. */
|
||
const etwas = d.bewegung?.some((m) => m.vor || m.zurueck || m.neu || m.geklaert);
|
||
if (etwas) {
|
||
$('mich-bewegung').replaceChildren(bewegungBauen(d.bewegung, d.spalten));
|
||
$('mich-lesehilfe').textContent =
|
||
'Nach oben: ein Punkt ist weitergekommen. Nach unten: er wird '
|
||
+ 'gerade anders gesehen als vorher – das ist kein Rückschritt, '
|
||
+ 'sondern meist nur ein genauerer Blick.';
|
||
$('mich-lesehilfe').hidden = false;
|
||
} else {
|
||
/* KEIN DIAGRAMM ÜBER NICHTS. Ein Raster aus leeren Spalten sieht
|
||
aus, als sei etwas kaputt. Ein Satz ist eine Auskunft. */
|
||
$('mich-bewegung').replaceChildren(leerKasten(
|
||
'Hier ist noch nichts zu sehen',
|
||
'Sobald jemand aus der Leitung eine Einschätzung setzt oder '
|
||
+ 'ändert, erscheint sie hier als Bewegung. Am Anfang ist diese '
|
||
+ 'Seite leer – das ist der Normalfall, kein Fehler.'));
|
||
$('mich-lesehilfe').hidden = true;
|
||
}
|
||
|
||
/* DER WEG WEITER. „Frag danach" ohne einen Ort ist eine Sackgasse
|
||
mit freundlichem Ton. */
|
||
const wege = $('mich-wege');
|
||
wege.replaceChildren();
|
||
const k1 = el('a', 'knopf knopf--klein', 'Deine Aufgaben ansehen');
|
||
k1.href = 'entwicklung.html';
|
||
const k2 = el('a', 'knopf knopf--klein w-weg--leise', 'Jemanden fragen');
|
||
k2.href = 'chat.html';
|
||
wege.append(k1, k2);
|
||
wege.hidden = false;
|
||
}
|
||
|
||
/** Ein Kasten für „hier ist noch nichts" — Überschrift plus Satz.
|
||
*
|
||
* ALS EIGENE FUNKTION, weil dieser Fall im Haus überall vorkommt und
|
||
* überall gleich aussehen soll. Ein leerer Zustand, der jedes Mal
|
||
* anders gebaut ist, liest sich wie ein Fehler. */
|
||
function leerKasten(titel, satz) {
|
||
const k = el('div', 'w-leer');
|
||
k.append(el('p', 'w-leer__titel', titel));
|
||
k.append(el('p', 'w-leer__satz', satz));
|
||
return k;
|
||
}
|
||
|
||
/* ---- Start ---------------------------------------------------------- */
|
||
$('zur-liste').addEventListener('click', () => { listeZeigen().catch(() => {}); });
|
||
|
||
(async () => {
|
||
try {
|
||
/* ERST DIE LISTE FRAGEN. Antwortet der Server mit 404, führt diese
|
||
Person das Team nicht — dann ist die eigene Sicht die richtige.
|
||
Die Rolle hier selbst zu prüfen hieße, dieselbe Entscheidung an
|
||
zwei Stellen zu treffen, und die zweite ist immer die, die
|
||
irgendwann etwas durchlässt. */
|
||
const leitung = await listeZeigen();
|
||
if (!leitung) { await michZeigen(); return; }
|
||
|
||
const p = new URLSearchParams(location.search).get('p');
|
||
if (p) await karteZeigen(Number(p));
|
||
} catch (f) {
|
||
if (String(f?.message || '') !== 'abgemeldet') {
|
||
melde('Die Seite lässt sich gerade nicht laden.');
|
||
}
|
||
}
|
||
})();
|
||
})();
|