Spicy sieht DogFathers Sachen nicht mehr -- und sechs Fehler dazu

VIER DINGE AUS DEN BILDSCHIRMFOTOS, jedes ein echter Fehler:

1. SPICY MEDIA SAH DOGFATHERS DATEN. Beim ersten Anlauf bekam die Rolle
   dieselbe Regel wie DogFather (`1=1`) -- damit stimmte der Ueberblick
   ueber Manager, Scouts und Creator, und nebenbei standen seine eigenen
   Termine, Aufgaben, Dateien und Eintraege mit drin. Jetzt gibt es
   ohneDogFather() als EINE Stelle dafuer: Zwei Spalten werden geprueft,
   `creator_id` (um wen geht es) und `erstellt_von` (wer hat es
   geschrieben) -- ein Termin, den er sich selbst anlegt, haengt nur an
   der zweiten. `IS NULL OR NOT IN` und nicht bloss `NOT IN`: In SQL ist
   `NULL NOT IN (...)` weder wahr noch falsch, die Zeile fiele
   stillschweigend heraus.

2. "PERSOENLICHER ZUGANGSCODE · undefined" auf der Anmeldeseite. Eine
   zweite Namensliste im Browser, die beim Hinzufuegen der Rolle
   niemand gepflegt hat. Ein unbekannter Schluessel faellt jetzt auf
   sich selbst zurueck statt auf `undefined` -- haesslich, aber
   sichtbar.

3. SPICY KAM NICHT IN DIE PERSONENVERWALTUNG. Der Server liess sie
   herein, das Skript warf sie wieder hinaus: zwei Listen fuer dieselbe
   Frage, gepflegt wurde nur die erste.

4. DIE ANMELDEKARTE WAR ZU KLEIN FUER FUENF ROLLEN. Der Kommentar an
   genau dieser Stelle warnt woertlich davor -- und ich habe getan,
   wovor er warnt: eine Rolle eingefuegt und die Zahl daneben nicht
   angefasst. Gemessen: Inhalt 573 px in einer 526 px hohen Tafel, die
   Fusszeile stand unter dem Rahmen. Schrift kleiner half nicht (sie lag
   schon auf dem Anschlag), also sind die Abstaende an neun Stellen
   enger. Nachgemessen auf fuenf Groessen: passt ueberall.

DIE ZEIT WAR WIRKLICH FALSCH. Nicht nur in meinem Satz: `datum()` in
personen.js schnitt die ISO-Zeichenkette ab -- und die ist UTC. Im
Protokoll stand 03:00, wo 05:00 war. Das Tueckische daran ist, dass es
nie kaputt aussieht: Eine Uhrzeit ist immer plausibel.

PROFILBILDER WERDEN JETZT GEZEICHNET. Der Server lieferte sie seit
gestern mit, gezeichnet wurden sie nirgends -- deshalb aenderte sich
nichts. Jetzt in der Personenauswahl (Aufgaben, Termine, Dateien,
Bereiche), in der Gespraechsliste und an jeder Nachricht. Der
Anfangsbuchstabe bleibt als Unterlage LIEGEN: Faellt das Bild aus, steht
dort weiter etwas Sinnvolles.

DER CHAT: Gesichter mit Rollenfarbe, Blasen mit Richtung (die erste
einer Folge eckig, die naechsten rund -- so sieht man, wo ein Gedanke
anfaengt), das offene Gespraech mit Schiene, Ungelesenes hervorgehoben,
und das Eingabefeld in einer eigenen Leiste, die sich beim Schreiben
hebt.

ZWEI EIGENE PATZER, beide von Pruefungen gefunden:
  - `o is not defined`: Mein Suchmuster hat die zwei Zeilen fuer das
    Bild ans DATEIENDE gesetzt statt in die Schleife -- und in
    kalender.js an einen <span> statt ans <option>. Gemeldet von
    pruef-sicht, das die Browserkonsole mitliest.
  - Ein Kommentar mit `bild` in schraegen Anfuehrungszeichen stand INNEN
    in einer Vorlagenzeichenkette und hat sie geschlossen. Die halbe SQL
    wurde zu Programmtext.
Dazu: `.chat-neu` gibt es nicht (heisst `.chat__eingabe`), und der
Rollenknopf hatte fuer den Manager keinen sichtbaren Fokus -- ein
box-shadow wird von `overflow: hidden` abgeschnitten, ein outline nicht.

Gruen: spicy (43), creator-anlegen (29), personen-liste (33), sicht (48),
chat (48), chat-optik (24), buehne (38), struktur (32), css-klassen (15),
handy (59), breiten (23), formulare (19), start-ansicht (136),
lesbarkeit (14), barrierefrei (18), tempo (8), kopf-messen (3),
glocke (26), haerte (20), manager-sicht (43), alle-wege (19),
aufgabenbrett (44), kalender (84).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-07 05:46:19 +02:00
co-authored by Claude Opus 5
parent 3b356d6eef
commit e8478c9cae
37 changed files with 695 additions and 197 deletions
+8
View File
@@ -772,6 +772,14 @@ Das laesst sich nicht rueckgaengig machen. Erledigte Aufgaben sollten normalerwe
for (const ziel of ['f-creator', 'f-verant', 'b-creator', 'b-verant']) {
const o = el('option', null, `${p.name} (${p.rolle})`);
o.value = String(p.id);
/* Bild ans <option> -- wahl.js zeichnet daraus das Gesicht.
Diese zwei Zeilen standen beim ersten Anlauf am DATEIENDE,
ausserhalb der Schleife: `o is not defined`, gemeldet von
pruef-sicht, das die Browserkonsole mitliest. Ein
Suchmuster ueber mehrere Dateien trifft eben nicht immer
die Stelle, die man meint. */
o.dataset.person = 'ja';
if (p.bild) o.dataset.bild = p.bild;
$(ziel).append(o);
}
}
+3
View File
@@ -533,6 +533,9 @@
for (const p of personen.filter((p) => p.rolle === 'creator')) {
const o = document.createElement('option');
o.value = String(p.id); o.textContent = p.name;
/* Bild ans <option> -- wahl.js zeichnet daraus das Gesicht. */
o.dataset.person = 'ja';
if (p.bild) o.dataset.bild = p.bild;
$('f-creator').append(o);
}
} catch { /* Zuordnung bleibt leer */ }
+22 -1
View File
@@ -129,7 +129,15 @@
} else {
zeichen.textContent = initialen(r.name);
const gegen = r.teilnehmer.find((t) => t.id !== ich.id);
if (gegen) zeichen.dataset.rolle = gegen.rolle;
if (gegen) {
zeichen.dataset.rolle = gegen.rolle;
if (gegen.bild) {
const b = document.createElement('img');
b.src = gegen.bild; b.alt = '';
b.addEventListener('error', () => b.remove());
zeichen.append(b);
}
}
}
knopf.append(zeichen);
@@ -233,9 +241,22 @@
letzterVon = n.von_id;
if (!gleicheFolge && !n.selbst) {
/* DAS GESICHT STATT DER INITIALEN (07.09.2026).
Der Anfangsbuchstabe bleibt als Unterlage LIEGEN und wird
nicht ersetzt: Laedt das Bild nicht, steht dort weiterhin
etwas Sinnvolles statt eines kaputten Bildsymbols. Dieselbe
Bauweise wie in der Kopfleiste und in der Personenauswahl --
drei Stellen, ein Verhalten. */
const z = el('span', 'chat-nachricht__zeichen', initialen(n.von));
z.setAttribute('aria-hidden', 'true');
z.dataset.rolle = n.rolle || '';
if (n.von_bild) {
const b = document.createElement('img');
b.src = n.von_bild; b.alt = '';
b.addEventListener('error', () => b.remove());
z.append(b);
}
zeile.append(z);
}
+2
View File
@@ -575,6 +575,8 @@
for (const feld of ['f-creator', 's-creator']) {
const o = document.createElement('option');
o.value = String(p.id); o.textContent = p.name;
o.dataset.person = 'ja';
if (p.bild) o.dataset.bild = p.bild;
$(feld).append(o);
}
}
+2
View File
@@ -404,6 +404,8 @@
const o = document.createElement('option');
o.value = String(p.id);
o.textContent = p.name;
o.dataset.person = 'ja';
if (p.bild) o.dataset.bild = p.bild;
$('f-creator').append(o);
}
} catch { /* Zuordnung bleibt leer */ }
+14 -2
View File
@@ -15,7 +15,19 @@
const knopf = $('knopf');
const fuer = $('fuer');
const NAMEN = { admin: 'DogFather', manager: 'Manager', scout: 'Scout', creator: 'Creator' };
/* Die fuenfte Rolle stand hier nicht -- deshalb las man unter dem
Codefeld "PERSOENLICHER ZUGANGSCODE · undefined" (07.09.2026).
Eine zweite Namensliste im Browser neben der im Server
(ROLLEN_NAME in workspace.js) ist genau die Stelle, die beim
naechsten Mal wieder vergessen wird. Sie bleibt hier trotzdem: Die
Anmeldeseite laeuft, BEVOR es eine Sitzung gibt, kann also nichts
abfragen. Damit es auffaellt, faellt ein unbekannter Schluessel
jetzt auf den Schluessel selbst zurueck statt auf `undefined` --
"spicy" waere haesslich gewesen, aber sichtbar. */
const NAMEN = { spicy: 'Spicy Media', admin: 'DogFather', manager: 'Manager',
scout: 'Scout', creator: 'Creator' };
const rollenName = (r) => NAMEN[r] || r || '';
const SPEICHER = 'dfw.rolle';
let gewaehlt = null;
@@ -24,7 +36,7 @@
function waehle(rolle, fokus = false) {
gewaehlt = rolle;
rollen.forEach((b) => b.setAttribute('aria-checked', String(b.dataset.rolle === rolle)));
fuer.textContent = `· ${NAMEN[rolle]}`;
fuer.textContent = `· ${rollenName(rolle)}`;
fuer.hidden = false;
melde('');
try { localStorage.setItem(SPEICHER, rolle); } catch { /* Privater Modus */ }
+6
View File
@@ -1338,6 +1338,12 @@
const o = document.createElement('option');
o.value = String(p.id);
o.textContent = p.name + ' (' + p.rolle + ')';
/* Beim ersten Anlauf landeten diese zwei Zeilen an einem
<span> in der Teilnehmerliste statt hier am <option> -- das
Suchmuster passte auf beide. Wirkungslos, aber irrefuehrend:
Es sah aus, als sei die Stelle erledigt. */
o.dataset.person = 'ja';
if (p.bild) o.dataset.bild = p.bild;
$('f-teilnehmer').append(o);
}
} catch { /* Zuordnung bleibt leer */ }
+34 -2
View File
@@ -7,7 +7,15 @@
/* DogFather und Manager haben dieselben Rechte -- die Unterscheidung
macht nur die Personenverwaltung, und die prueft der Server. */
const LEITUNG = new Set(['admin', 'manager']);
/* 'spicy' gehoert dazu (07.09.2026). Ohne diesen Eintrag schickte die
Seite Spicy Media sofort auf die Startseite zurueck -- der Server
liess sie herein, das Skript warf sie wieder hinaus. Genau der
Fehler, den Filipe gemeldet hat: "bei spicy funktioniert dieser
button nicht, wenn sie drauf druecken geht es nicht auf."
Zwei Listen fuer dieselbe Frage, eine im Server und eine hier --
und beim Hinzufuegen einer Rolle wurde nur die erste gepflegt. */
const LEITUNG = new Set(['spicy', 'admin', 'manager']);
const $ = (id) => document.getElementById(id);
const ROLLENNAME = { admin: 'DogFather', manager: 'Manager', scout: 'Scout', creator: 'Creator' };
@@ -127,7 +135,31 @@
return a;
}
const datum = (iso) => (iso ? iso.slice(0, 16).replace('T', ' ') : '—');
/* ORTSZEIT, NICHT UTC (07.09.2026).
Hier stand `iso.slice(0, 16).replace('T', ' ')` -- also ein Schnitt
durch die Zeichenkette. Der Server schreibt seine Zeitstempel in
UTC; im Sommer sind das ZWEI STUNDEN weniger. Im Protokoll stand
deshalb "03:00", wo tatsaechlich 05:00 war. Filipe hat es gemeldet:
"wie 3 uhr, wir haben 5 uhr."
Das Tueckische daran: Es sieht nie kaputt aus. Eine Uhrzeit ist
immer plausibel -- man merkt es nur, wenn man zufaellig gerade auf
die Uhr sieht. Und im Winter waere es nur eine Stunde, also noch
schwerer zu bemerken.
`new Date(iso)` liest den Zeitstempel richtig und gibt ihn in der
Zeit des Geraets aus. Ein Schnitt durch die Zeichenkette ist
ausschliesslich bei einem reinen DATUM erlaubt ("2026-09-07") --
dort gibt es keine Zeitzone, um die man sich irren koennte. */
const datum = (iso) => {
if (!iso) return '—';
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return '—';
const z = (n) => String(n).padStart(2, '0');
return `${d.getFullYear()}-${z(d.getMonth() + 1)}-${z(d.getDate())} `
+ `${z(d.getHours())}:${z(d.getMinutes())}`;
};
/* ---------- Code anzeigen -------------------------------------------- */
+37 -1
View File
@@ -237,7 +237,43 @@
zeilen.append(el('p', 'wahl2__gruppe', gruppe));
vorherigeGruppe = gruppe;
}
const zeile = el('button', 'wahl2__eintrag', o.textContent);
/* DAS GESICHT ZUM NAMEN (07.09.2026).
Wunsch Filipe: "jeder soll auch immer das profilbild von denen
sehen mit denen sie verbunden sind ... ich seh als
manager/betreuer von tili ihr profilbild immer noch nicht."
Der Server liefert die Bildadresse seit heute mit
(/api/personen). Gezeichnet wurde sie nirgends -- deshalb
aenderte sich fuer ihn nichts. Hier ist der wirksamste Ort:
Diese Liste ist die Personenauswahl in Aufgaben, Terminen,
Dateien, Bereichen und Calls. Ein Bild macht aus einer Liste
von Namen eine Liste von Menschen, und man trifft den
richtigen, ohne zu lesen.
Der ANFANGSBUCHSTABE bleibt als Unterlage LIEGEN und wird
nicht ersetzt: Laedt das Bild nicht (Verbindung weg, Datei
geloescht), steht dort weiterhin etwas Sinnvolles statt eines
kaputten Bildsymbols. Genau so macht es die Kopfleiste auch.
Wo es kein `data-bild` gibt (Kategorien, Saeulen, Takte),
bleibt die Zeile wie bisher -- die Auswahl weiss nicht, dass
sie manchmal Personen zeigt, und muss es auch nicht. */
const zeile = el('button', 'wahl2__eintrag');
if (o.dataset.bild || o.dataset.person === 'ja') {
const p = el('span', 'wahl2__bild');
p.setAttribute('aria-hidden', 'true');
p.textContent = (o.textContent || '?').trim().charAt(0).toUpperCase();
if (o.dataset.bild) {
const img = document.createElement('img');
img.src = o.dataset.bild;
img.alt = '';
img.addEventListener('error', () => img.remove());
p.append(img);
}
zeile.append(p);
}
zeile.append(el('span', 'wahl2__name', o.textContent));
zeile.type = 'button';
zeile.setAttribute('role', 'option');
const an = o.selected && !z.feld.dataset.extern;