Eine Nachricht, die nicht ankommt, sieht man jetzt -- und schickt sie neu

DER KOMMENTAR STAND DA, DIE SACHE NICHT. Im Chat stand woertlich:
"NICHT STILL VERSCHWINDEN LASSEN. Wer etwas schreibt und es sieht,
glaubt, es sei angekommen." Gemessen: `markiereAlsGescheitert` setzte
`n.gescheitert = true` -- und gelesen hat das Merkmal NIEMAND, weder
das Skript noch das CSS. Eine gescheiterte Nachricht sah exakt aus wie
eine zugestellte. Im CSS stand an der Stelle eine leere Regel mit dem
Kommentar "Platzhalter, damit :has unterstuetzt bleibt".

Ein Kommentar, der eine Absicht beschreibt, erfuellt sie nicht.

Jetzt: sichtbare Kante an der Blase, blasse Darstellung solange sie
unterwegs ist, und ein Knopf "Nochmal senden" daneben. Der Satz in der
Meldezeile bat vorher ums Kopieren -- fuenf Handgriffe fuer etwas, das
die Seite in einem tun kann; den Text hat sie ja noch.

KEIN ZWEITER SENDEWEG: Der Knopf legt den Text zurueck ins Feld,
stellt das Antwortziel wieder her und ruft `abschicken`. Ein eigener
Weg waere morgen anders als dieser, und der Unterschied fiele erst
auf, wenn er zaehlt.

"ZUM NEUESTEN" STAND AUF EINER RECHNUNG VON GESTERN: `bottom: 96px` --
die Hoehe der Schreibleiste an dem Tag, an dem der Knopf gebaut wurde.
Das Textfeld waechst aber bis 160. Wer lange tippt und gleichzeitig
oben liest, hatte den Knopf HINTER der Leiste. Und darueber koennen
noch das Nachtruhe-Band und der Hochlade-Balken stehen.
Jetzt misst die Seite den ganzen Unterbau selbst -- ueber einen
Beobachter statt einer Liste von Ausloesern, denn das Band erscheint
um Mitternacht von selbst. Kommt morgen ein viertes Bauteil dazu,
rechnet es von allein mit.

NEUE PRUEFUNG FUER EINEN WEG, DEN NIE ETWAS GEPRUEFT HAT:
pruef-chat-optik faengt die Sendeanfrage jetzt einmal ab und geht den
ganzen Fehlerweg durch -- 10 Aussagen, darunter:
  der Unterschied ist auch zu SEHEN, nicht nur im Merkmal (Rand 1px)
  die Nachricht steht danach GENAU EINMAL da, nicht doppelt
  und sie ist beim Gegenueber angekommen
Der letzte ist der eigentliche: Alles davor koennte gut aussehen und
trotzdem nichts zugestellt haben.

pruef-treffchat war seit gestern rot -- 8 Fehler ueber "undefined".
Sie suchte die Kachel mit `ziel === "chat.html"`, seit dem 19.09.
heisst es `chat.html?raum=treff` (sie fuehrt direkt in den Raum). Kein
Befund ueber das Haus, sondern einer ueber sich selbst. Sie sucht
jetzt nach der SEITE und prueft statt des Wortlauts das, worauf es
ankommt: Man erkennt den Chat, und der Name kommt genau einmal vor.
110/0 statt 108 mit 8 Fehlern.

pruef-chat, -anhaenge, -kanaele, -optik, -ausbau gruen,
pruef-start-ansicht 151/0, pruef-tippziele 11/0, pruef-nachfrage 33/0,
pruef-leerzustand 13/0, pruef-meldungen 8/0, pruef-css-klassen gruen.
This commit is contained in:
2026-09-20 12:44:31 +02:00
parent 18148675a9
commit 0a2363374c
41 changed files with 807 additions and 520 deletions
+119 -1
View File
@@ -990,6 +990,24 @@
zeile.dataset.id = String(n.id);
if (n.selbst) zeile.dataset.selbst = 'ja';
if (n.zurueckgenommen) zeile.dataset.weg = 'ja';
/* UNTERWEGS UND GESCHEITERT SIND ZWEI ZUSTÄNDE -- UND BEIDE
WAREN UNSICHTBAR (20.09.2026).
------------------------------------------------------------
`markiereAlsGescheitert` setzte `n.gescheitert = true` und
zeichnete neu. Gelesen hat das Merkmal NIEMAND: weder das
Skript noch das CSS. Eine gescheiterte Nachricht sah exakt
aus wie eine zugestellte.
Daneben stand der Kommentar „NICHT STILL VERSCHWINDEN LASSEN.
Wer etwas schreibt und es sieht, glaubt, es sei angekommen."
Genau das ist trotzdem passiert -- der Satz in der Meldezeile
verschwindet nach kurzer Zeit, die Nachricht bleibt stehen
und sieht gesendet aus.
Ein Kommentar, der eine Absicht beschreibt, erfüllt sie
nicht. */
if (n.unterwegs) zeile.dataset.unterwegs = 'ja';
if (n.gescheitert) zeile.dataset.gescheitert = 'ja';
/* Mehrere Nachrichten derselben Person hintereinander rücken
zusammen und wiederholen den Namen nicht -- sonst liest man
fünfmal "Patrick" und dreimal dasselbe Gesicht. */
@@ -1138,6 +1156,33 @@
if (Array.isArray(n.reaktionen) && n.reaktionen.length) {
blase.append(reaktionenBauen(n));
}
/* NOCHMAL SENDEN, STATT „kopiere sie und versuch es erneut".
------------------------------------------------------------
Dort stand die Seite bat den Nutzer, ihre Arbeit zu machen:
Text markieren, kopieren, Feld leeren, einfügen, senden. Fünf
Handgriffe für etwas, das die Seite in einem tun kann -- sie
hat den Text ja noch. */
if (n.gescheitert) {
const nochmal = el('button', 'chat-nachricht__nochmal', 'Nochmal senden');
nochmal.type = 'button';
nochmal.addEventListener('click', async () => {
nochmal.disabled = true;
nochmal.textContent = 'wird gesendet …';
/* DENSELBEN WEG GEHEN WIE VON HAND -- nur ohne die fünf
Handgriffe. Text zurück ins Feld, Antwortziel wieder
setzen, `abschicken` aufrufen. Kein zweiter Sendeweg:
Ein eigener wäre morgen anders als dieser, und der
Unterschied fiele erst auf, wenn er zählt. */
const weg = nachrichten.findIndex((x) => x.id === n.id);
if (weg >= 0) nachrichten.splice(weg, 1);
$('text').value = n.text;
if (n.antwort) antwortSetzen(n.antwort);
melde('');
await abschicken({ preventDefault() {} });
});
blase.append(nochmal);
}
zeile.append(blase);
ziel.append(zeile);
}
@@ -1414,7 +1459,10 @@
const i = nachrichten.findIndex((n) => n.id === id);
if (i >= 0) { nachrichten[i].gescheitert = true; delete nachrichten[i].unterwegs; }
verlaufZeichnen();
melde(`${grund} Die Nachricht steht noch da – kopiere sie und versuch es erneut.`);
/* Kein „kopiere sie" mehr -- an der Nachricht steht jetzt ein
Knopf, der es in einem Griff wiederholt. */
melde(`${grund} Die Nachricht ist nicht angekommen – `
+ `mit „Nochmal senden" kannst du es gleich wiederholen.`);
}
async function zuruecknehmen(id) {
@@ -1440,6 +1488,74 @@
const f = $('text');
f.style.height = 'auto';
f.style.height = Math.min(f.scrollHeight, 160) + 'px';
/* DIE LEISTE SAGT, WIE HOCH SIE IST (20.09.2026).
Der Knopf „Zum neuesten" schwebte auf festen 96 Pixeln -- der
Höhe der Leiste an dem Tag, an dem er gebaut wurde. Wächst das
Textfeld (bis 160), verschwindet er dahinter, und zwar genau
dann, wenn man lange tippt und gleichzeitig oben liest.
Gemessen statt gerechnet: Eine Zahl, die das Element selbst
kennt, kann nicht veralten. */
unterbauMessen();
}
/** Wie hoch ist alles, was UNTEN am Verlauf klebt?
*
* Nicht nur die Schreibleiste: Darüber können das Nachtruhe-Band
* und der Hochlade-Balken stehen. Wer nur die Leiste misst, legt
* den Knopf „Zum neuesten" nachts hinter das Band.
*
* Gemessen wird vom unteren Rand des Kastens bis zur obersten
* Kante des sichtbaren Unterbaus -- egal, aus wie vielen Teilen er
* gerade besteht. Kommt morgen ein viertes dazu, rechnet es von
* selbst mit. */
function unterbauMessen() {
const leiste = $('schreiben');
const kasten = leiste?.parentElement;
if (!kasten) return;
const unten = kasten.getBoundingClientRect().bottom;
let oberste = unten;
for (const id of ['nachtruhe', 'anhang-fortschritt', 'schreiben']) {
const e = $(id);
if (!e || e.hidden) continue;
const r = e.getBoundingClientRect();
if (r.height > 0 && r.top < oberste) oberste = r.top;
}
const hoch = Math.max(0, Math.round(unten - oberste));
/* NUR SETZEN, WENN SICH ETWAS AENDERT. Sonst beisst sich der
Beobachter unten in den eigenen Schwanz: Eine Stil-Aenderung am
Kasten ist selbst wieder eine Aenderung. */
if (hoch === letzteLeiste) return;
letzteLeiste = hoch;
kasten.style.setProperty('--leiste', hoch + 'px');
}
let letzteLeiste = -1;
/** DER UNTERBAU AENDERT SICH AUCH OHNE TIPPEN.
*
* Das Nachtruhe-Band erscheint um Mitternacht von selbst, der
* Hochlade-Balken beim Anhaengen einer Datei. `hoeheAnpassen`
* laeuft dabei nicht -- der Knopf „Zum neuesten" staende dann
* wieder hinter etwas.
*
* Ein Beobachter statt einer Liste von Ausloesern: Was sich am
* unteren Rand tut, wird bemerkt, auch wenn es morgen ein viertes
* Bauteil ist. Gerechnet wird hoechstens einmal je Bild -- sonst
* waere jede getippte Zeile eine Messung. */
function unterbauBeobachten() {
const kasten = $('schreiben')?.parentElement;
if (!kasten || !window.MutationObserver) return;
let geplant = false;
new MutationObserver(() => {
if (geplant) return;
geplant = true;
requestAnimationFrame(() => { geplant = false; unterbauMessen(); });
}).observe(kasten, {
childList: true, subtree: true,
attributes: true, attributeFilter: ['hidden', 'class'],
});
/* Und beim Drehen des Geräts: Dort ändert sich nichts am Aufbau,
aber alles an der Breite -- und damit an der Zeilenzahl. */
window.addEventListener('resize', () => unterbauMessen());
}
/* ---------- Gelesen ------------------------------------------------------ */
@@ -2327,6 +2443,8 @@
await raeumeLaden();
stromOeffnen();
unterbauBeobachten();
unterbauMessen();
/* Kommt man aus einer Benachrichtigung, steht das Gespräch in der
Adresse -- dann direkt hinein. */