Einmal anmelden reicht -- und beim Zurueckgehen bleibt die Seite stehen
Filipe: "kannst du bitte machen dass die leute sich nur einmal anmelden
muessen und dan nur noch abgemeldet werden wenn sie sich selbst
abmelden. damit sie auch die benarichtigungen sofort kriegen ... und
auch sofort die anrufe annehmen koennen."
---- 1. DIE ANMELDUNG BLEIBT ----------------------------------------
VORHER: zwoelf Stunden, feste Frist ab dem Anmelden. Wer morgens um
acht anfing, flog abends um acht raus -- mitten im Betrieb. Und wer
abgemeldet ist, hat die Seite nicht offen; ein Anruf erreicht ihn dann
nur noch ueber die Benachrichtigung, und bis er sich wieder angemeldet
hat, ist das Klingeln vorbei. Genau das beschreibt Filipe.
JETZT: ein gleitendes Fenster von 180 Tagen, das sich bei jeder Nutzung
verlaengert. Wer die Seite benutzt, bleibt angemeldet -- ohne Ende.
NICHT UNENDLICH, und das ist Absicht: In diesem Haus liegen
vertrauliche Meldungen ueber Menschen. Ein Zugang, der nie ablaeuft,
ist auf einem verlorenen Handy fuer immer offen. Ein halbes Jahr ohne
Besuch schliesst das Geraet und ist niemandem zu viel zugemutet.
An EINER Stelle gebaut: `sitzungLesen` -- durch die gehen alle 26
Fachmodule. Ein Parameter mehr haette 26 Aufrufe geaendert und beim 27.
Modul vergessen werden koennen; `req.res` haengt ohnehin an der
Anfrage. Verlaengert wird erst, wenn weniger als die Haelfte des
Fensters uebrig ist -- sonst waere das ein Schreibzugriff bei jedem
Bild und jedem Herzschlag des Ereignisstroms.
Drei Texte, die noch "zwoelf Stunden" behaupteten, sagen es jetzt
richtig -- inklusive der Abmelde-Nachfrage, die jetzt dazusagt, dass
man angemeldet bleiben SOLLTE, um Anrufe zu bekommen.
---- 2. BEIM ZURUECKGEHEN BLEIBT DIE STELLE -------------------------
Filipe, zum dritten Mal und in Grossbuchstaben. Also erst gemessen:
gescrollt auf: 900
gemerkt: 900
gelandet: 1054 <- 154 px daneben, zuverlaessig
Die Wiederherstellung LIEF also -- sie traf nur nicht. Ursache ist
Chromes Scroll-Verankerung: Waechst Inhalt OBERHALB der Stelle,
verschiebt der Browser den Bildlauf mit, damit das Sichtbare stehen
bleibt. Im Alltag genau richtig; beim Wiederherstellen das Gegenteil.
Sie wird jetzt fuer die Dauer des Wiederherstellens abgeschaltet und
danach wieder eingeschaltet -- nicht dauerhaft, sonst spraenge einem im
Chat der Text unter dem Finger weg. Dazu wird die Stelle nachgesetzt,
solange die Seite noch waechst, und aufgehoert, sobald sie 400 ms lang
ruhig ist. Ergebnis: 900 -> 900, und es bleibt dort.
DAZU, und das ist der groessere Teil: Der Ereignisstrom in kopf.js
laeuft auf 32 Seiten und wird jetzt beim Weggehen geschlossen. Eine
offene EventSource sperrt den Vor-/Zurueck-Speicher des Browsers aus --
deshalb wurde bisher JEDE Rueckkehr ein vollstaendiger Neuaufbau.
Filipe hat genau das beschrieben ("OHNE DASS DIE SEITE ... NEU LAEDT").
EHRLICH DAZU: Playwright schaltet diesen Speicher fuer Tests ab, und er
liess sich hier nicht einschalten. Ich kann also NICHT messen, dass er
jetzt greift -- die Aenderung ist trotzdem richtig (eine offene
Verbindung ist die dokumentierte Sperre, und ein Strom, der beim
Weggehen offen bleibt, ist ohnehin ein Zuhoerer, den niemand mehr
liest). Gemessen und abgesichert ist der Weg OHNE diesen Speicher --
also der schlechteste Fall.
---- 3. DABEI GEFUNDEN: pruef-schranke mass seit neun Tagen nichts --
Sie suchte `const GESCHUETZT = {` per Textmuster in workspace.js. Diese
Tabelle ist am 11.09.2026 nach rechte.js umgezogen -- seitdem fand das
Muster nichts, und die Pruefung meldete "0 geschuetzte Seiten", "alle 0
Seiten leiten um", "0 Schreibweisen ausprobiert". Drei rote Zeilen, die
nach einem Zaehlfehler aussahen und in Wahrheit hiessen: hier wird
nichts mehr geprueft.
Jetzt wird die Tabelle IMPORTIERT. Ein Textmuster auf fremden
Quelltext reisst beim naechsten Umzug still; ein import reisst laut.
Ergebnis: 33 geschuetzte Seiten, 363 Schreibweisen -- alles gruen.
GEPRUEFT: pruef-rollstelle 8/0 (neu, mit Spur ueber die Zeit und zwei
Gegenproben), pruef-schranke (war rot), pruef-code 17/0,
pruef-haerte 20/0.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -41,7 +41,7 @@
|
||||
{ name: 'Aufgaben aus Gesprächen',
|
||||
text: 'To-dos aus einem Protokoll, Entscheidungen aus einem Review und Befunde aus dem Start-Check werden zu echten Aufgaben im Brett – in einem Zug, nicht als Text, den man später abtippt.' },
|
||||
{ name: 'Sitzungen laufen ab',
|
||||
text: 'Eine Anmeldung gilt zwölf Stunden. Nach einem neuen Zugangscode oder einer Sperre enden alle offenen Sitzungen der Person sofort.' },
|
||||
text: 'Eine Anmeldung bleibt bestehen, bis man sich selbst abmeldet – sie verlängert sich bei jeder Nutzung. Nach einem halben Jahr ohne Besuch, nach einem neuen Zugangscode oder einer Sperre endet sie.' },
|
||||
{ name: 'Schutz vor Rateversuchen',
|
||||
text: 'Acht Fehlversuche je Anschluss in zehn Minuten, danach ist zu. Gezählt wird die echte Adresse, nicht die von Cloudflare.' },
|
||||
{ name: 'Sicherung vor Umbauten',
|
||||
|
||||
+134
-35
@@ -992,15 +992,19 @@
|
||||
kommt ohne einen neuen Code von DogFather nicht zurueck.
|
||||
|
||||
Deshalb steht genau das im Kasten -- nicht "Sicher?". Und
|
||||
daneben die Auskunft, dass Zuklappen reicht: Die Sitzung
|
||||
haelt 12 Stunden, abmelden muss man sich zum Weggehen gar
|
||||
nicht. */
|
||||
daneben die Auskunft, dass Zuklappen reicht: Seit dem
|
||||
20.09.2026 bleibt man angemeldet, bis man sich selbst
|
||||
abmeldet -- abmelden muss man sich zum Weggehen also gar
|
||||
nicht, und man SOLLTE es auch nicht, weil man sonst Anrufe
|
||||
und Benachrichtigungen verpasst. Genau das steht jetzt im
|
||||
Kasten. */
|
||||
if (!await frageNach({
|
||||
titel: 'Abmelden?',
|
||||
was: 'Zum Wiederanmelden brauchst du deinen persönlichen '
|
||||
+ 'Zugangscode.',
|
||||
bleibt: 'Wenn du nur kurz weg willst: Einfach zumachen reicht – '
|
||||
+ 'du bleibst 12 Stunden angemeldet.',
|
||||
+ 'du bleibst angemeldet, bis du dich selbst abmeldest. '
|
||||
+ 'Solange bekommst du Anrufe und Nachrichten.',
|
||||
ja: 'Abmelden',
|
||||
})) return;
|
||||
abmelden.disabled = true;
|
||||
@@ -1105,26 +1109,80 @@
|
||||
eine Zahl, die sich selten ändert. */
|
||||
if (window.EventSource) {
|
||||
try {
|
||||
const strom = new EventSource('/workspace/api/chat/strom');
|
||||
strom.addEventListener('neu', holen);
|
||||
/* Nach einem Abriss verbindet der Browser selbst neu -- dann
|
||||
einmal nachzählen, was inzwischen kam.
|
||||
/* ==== DER STROM MUSS BEIM WEGGEHEN ZUGEMACHT WERDEN =============
|
||||
(20.09.2026)
|
||||
|
||||
NUR AB DEM ZWEITEN MAL (06.09.2026). Hier stand schlicht
|
||||
`strom.addEventListener('open', holen)` -- und damit fragte
|
||||
JEDE Seite die Zahl zweimal ab: einmal direkt beim Laden und
|
||||
einmal Millisekunden später, sobald der Strom aufging.
|
||||
Gemessen von pruef-tempo-workspace auf fünf Seiten.
|
||||
Filipe, zum dritten Mal: "wenn ich in eine kategorie rein geh
|
||||
und dan zurueck, geht die hauptseite immer wieder ganz hoch ...
|
||||
OHNE DASS DIE SEITE HOCH SCROLLT ODER NEU LAEDT."
|
||||
|
||||
Zwei Anfragen für dieselbe Zahl sind kein Fehler, den jemand
|
||||
bemerkt -- und genau deshalb bleibt so etwas jahrelang
|
||||
stehen. Bei zehn Angemeldeten ist es die doppelte Last für
|
||||
nichts. */
|
||||
let schonOffen = false;
|
||||
strom.addEventListener('open', () => {
|
||||
if (schonOffen) holen();
|
||||
schonOffen = true;
|
||||
Der zweite Teil ist der eigentliche: Die Seite LAEDT NEU. Und
|
||||
daran war dieser Strom schuld.
|
||||
|
||||
Browser haben einen Vor-/Zurueck-Speicher (bfcache): Beim
|
||||
Zurueckgehen wird die alte Seite nicht neu gebaut, sondern
|
||||
eingefroren wieder aufgeweckt -- mit allem, auch mit der
|
||||
Bildlaufstelle, sofort und ohne einen einzigen Abruf. Genau
|
||||
das, was Filipe beschreibt.
|
||||
|
||||
EINE OFFENE EventSource SPERRT DIESEN SPEICHER AUS. Das ist
|
||||
keine Vermutung, sondern die ausdrueckliche Regel von Chrome
|
||||
und Firefox: Eine Seite mit offener Verbindung darf nicht
|
||||
eingefroren werden, weil der Server sonst auf einen Zuhoerer
|
||||
schreibt, den es nicht mehr gibt.
|
||||
|
||||
Dieser Strom laeuft auf 32 Seiten -- also auf allen. Damit war
|
||||
JEDE Rueckkehr ein vollstaendiger Neuaufbau: Seite leer, Inhalt
|
||||
nachladen, Stelle verloren. Das Nachspringen weiter unten hat
|
||||
danach repariert, was gar nicht haette kaputtgehen muessen.
|
||||
|
||||
JETZT: Beim Weggehen (`pagehide`) wird zugemacht, beim
|
||||
Zurueckkommen (`pageshow`) neu aufgemacht -- und dann einmal
|
||||
nachgezaehlt, weil in der Zwischenzeit etwas gekommen sein
|
||||
kann. Fuer den Menschen aendert sich nichts, ausser dass
|
||||
Zurueckgehen sofort geht.
|
||||
|
||||
`persisted` IST DIE ANTWORT AUF "kam sie aus dem Speicher?".
|
||||
Nur dann muss neu verbunden werden; bei einem gewoehnlichen
|
||||
Laden hat der Aufbau oben das schon getan. */
|
||||
let strom = null;
|
||||
|
||||
const stromAuf = () => {
|
||||
if (strom) return;
|
||||
strom = new EventSource('/workspace/api/chat/strom');
|
||||
strom.addEventListener('neu', holen);
|
||||
/* NUR AB DEM ZWEITEN MAL (06.09.2026). Hier stand schlicht
|
||||
`addEventListener('open', holen)` -- und damit fragte JEDE
|
||||
Seite die Zahl zweimal ab: einmal direkt beim Laden und
|
||||
einmal Millisekunden spaeter, sobald der Strom aufging.
|
||||
Gemessen von pruef-tempo-workspace auf fuenf Seiten.
|
||||
|
||||
Zwei Anfragen fuer dieselbe Zahl sind kein Fehler, den
|
||||
jemand bemerkt -- und genau deshalb bleibt so etwas
|
||||
jahrelang stehen. Bei zehn Angemeldeten ist es die
|
||||
doppelte Last fuer nichts. */
|
||||
let schonOffen = false;
|
||||
strom.addEventListener('open', () => {
|
||||
if (schonOffen) holen();
|
||||
schonOffen = true;
|
||||
});
|
||||
};
|
||||
const stromZu = () => {
|
||||
if (!strom) return;
|
||||
try { strom.close(); } catch { /* egal */ }
|
||||
strom = null;
|
||||
};
|
||||
|
||||
window.addEventListener('pagehide', stromZu);
|
||||
window.addEventListener('pageshow', (e) => {
|
||||
if (!e.persisted) return; /* gewoehnliches Laden: schon auf */
|
||||
stromAuf();
|
||||
holen(); /* was kam, waehrend wir weg waren? */
|
||||
});
|
||||
|
||||
stromAuf();
|
||||
/* Ab hier nur noch der alte Aufbau als Erklaerung -- die
|
||||
Zuhoerer haengen jetzt in stromAuf(). */
|
||||
} catch { /* dann eben nur beim Laden */ }
|
||||
}
|
||||
|
||||
@@ -1881,28 +1939,69 @@
|
||||
/* WER SELBST SCROLLT, GEWINNT. `wheel`, `touchstart` und die
|
||||
Tastatur -- alle drei, weil es alle drei Arten gibt, eine Seite
|
||||
zu bewegen. */
|
||||
var aufhoeren = function () { abgebrochen = true; };
|
||||
var aufhoeren = function () { abgebrochen = true; aufraeumen(); };
|
||||
window.addEventListener('wheel', aufhoeren, { passive: true, once: true });
|
||||
window.addEventListener('touchstart', aufhoeren, { passive: true, once: true });
|
||||
window.addEventListener('keydown', aufhoeren, { once: true });
|
||||
|
||||
/* ==== DIE VERANKERUNG MUSS WEG, SOLANGE WIR ZIELEN (20.09.2026) ===
|
||||
|
||||
GEMESSEN: gemerkt 900, gelandet 1054 -- 154 Pixel daneben, und
|
||||
zwar zuverlaessig. Die Erklaerung ist Chromes Scroll-Verankerung:
|
||||
Waechst Inhalt OBERHALB der Stelle, an der man steht, verschiebt
|
||||
der Browser den Bildlauf mit, damit das Sichtbare stehen bleibt.
|
||||
Im Alltag ist das genau richtig -- beim Wiederherstellen ist es
|
||||
das Gegenteil: Wir setzen 900, danach laedt der Inhalt darueber
|
||||
nach, und die Verankerung rechnet unsere 900 in 1054 um.
|
||||
|
||||
Sie wird deshalb fuer die Dauer des Wiederherstellens
|
||||
ausgeschaltet und danach wieder eingeschaltet. Nicht dauerhaft:
|
||||
Beim Nachladen im Chat oder in langen Listen ist sie das, was
|
||||
verhindert, dass einem der Text unter dem Finger wegspringt. */
|
||||
var wurzel = document.documentElement;
|
||||
var vorherAnker = wurzel.style.overflowAnchor;
|
||||
wurzel.style.overflowAnchor = 'none';
|
||||
var aufgeraeumt = false;
|
||||
function aufraeumen() {
|
||||
if (aufgeraeumt) return;
|
||||
aufgeraeumt = true;
|
||||
wurzel.style.overflowAnchor = vorherAnker || '';
|
||||
}
|
||||
|
||||
/* Wie hoch war das Dokument beim letzten Versuch? Solange sich das
|
||||
aendert, ist die Seite noch am Aufbauen -- und solange wird die
|
||||
Stelle nachgesetzt. Erst wenn sie eine Weile gleich bleibt, ist
|
||||
fertig. */
|
||||
var letzteHoehe = -1;
|
||||
var ruhigSeit = 0;
|
||||
var RUHIG_MS = 400;
|
||||
|
||||
function hoeheJetzt() {
|
||||
return Math.max(
|
||||
document.body ? document.body.scrollHeight : 0,
|
||||
wurzel ? wurzel.scrollHeight : 0);
|
||||
}
|
||||
|
||||
(function versuchen() {
|
||||
if (abgebrochen) return;
|
||||
var hoch = Math.max(
|
||||
document.body ? document.body.scrollHeight : 0,
|
||||
document.documentElement ? document.documentElement.scrollHeight : 0);
|
||||
/* Reicht die Höhe schon? Dann hin und fertig. */
|
||||
var hoch = hoeheJetzt();
|
||||
|
||||
if (hoch >= stand.y + window.innerHeight * 0.5) {
|
||||
window.scrollTo(0, stand.y);
|
||||
/* NOCH EINMAL IM NÄCHSTEN BILD. Manche Listen wachsen beim
|
||||
Zeichnen noch ein Stück (Bilder, Schriften), und dann sitzt
|
||||
der erste Sprung ein paar Pixel daneben. */
|
||||
requestAnimationFrame(function () {
|
||||
if (!abgebrochen) window.scrollTo(0, stand.y);
|
||||
});
|
||||
return;
|
||||
/* Nur setzen, wenn wir nicht ohnehin schon dort stehen -- ein
|
||||
scrollTo je Bild waere sonst 60-mal in der Sekunde. */
|
||||
if (Math.abs((window.scrollY || 0) - stand.y) > 1) {
|
||||
window.scrollTo(0, stand.y);
|
||||
}
|
||||
if (hoch === letzteHoehe) {
|
||||
if (!ruhigSeit) ruhigSeit = Date.now();
|
||||
/* Lange genug ruhig -> die Seite steht. */
|
||||
if (Date.now() - ruhigSeit >= RUHIG_MS) { aufraeumen(); return; }
|
||||
} else {
|
||||
letzteHoehe = hoch;
|
||||
ruhigSeit = 0;
|
||||
}
|
||||
}
|
||||
if (Date.now() > bis) return; /* Aufgeben ist auch eine Antwort. */
|
||||
if (Date.now() > bis) { aufraeumen(); return; } /* Aufgeben ist auch eine Antwort. */
|
||||
requestAnimationFrame(versuchen);
|
||||
})();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user