Benachrichtigungen wecken das Telefon wieder: Urgency je Art

Diene hat gemeldet: "Die Benachrichtigungen werden nicht angezeigt,
wenn neue Nachrichten reinkommen. Erst, wenn man die App oeffnet."

Alles Messbare war gruen: Der Service Worker zeigt die Meldung bei
geschlossener App (pruef-push-zu, 13 Pruefungen), jede Anmeldung im
Haus stand auf fehler = 0, der Push-Dienst quittierte mit 201. Nur
den Kopf auf der Leitung hat nie jemand gemessen:

    Urgency: normal

Beide Dienste behandeln das ausdruecklich als "darf warten".
Android/FCM haelt solche Meldungen zurueck, solange das Telefon
doest, und stellt sie zu, wenn es aufwacht -- typischerweise beim
Entsperren oder Oeffnen der App. Apple/APNs nennt es "verzoegert,
gebuendelt oder gedrosselt". Das ist Dienes Satz, Wort fuer Wort,
und es stand als Vorgabe im eigenen Quelltext.

zuletzt_ok konnte das nie zeigen: Es beweist, dass der DIENST
angenommen hat, nicht dass das GERAET etwas angezeigt hat.

Bis hierher hing die Dringlichkeit an der Ruheregel (dringend =
regel === "nie"), mit der Begruendung, "darf das nachts stoeren?"
und "darf das warten?" haetten dieselbe Antwort. Haben sie nicht:
Ein Chat um 3 Uhr soll schweigen, um 14 Uhr aber nicht vierzig
Minuten liegen bleiben. Jetzt zwei Felder -- in DERSELBEN Zeile
derselben Artenliste, damit sie nicht auseinanderlaufen koennen.

10 von 19 Arten sind eilig (Anruf, Chat, Erwaehnung, Support, Hilfe,
Termin, Wecker, zweimal Live, Probe). Die anderen neun duerfen
warten -- waere alles eilig, waere nichts mehr eilig.

DIE FALLE BEIM BEHEBEN: dringend steuerte auch die Haltbarkeit
(TTL 150). Ein einfach auf "dringend" gestellter Chat waere nach
150 s verfallen -- wer sein Telefon drei Minuten aus hat, haette die
Nachricht GAR nicht mehr bekommen. Aus "zu spaet" waere "nie"
geworden. Deshalb sind eilig und kurzlebig getrennt; kurzlebig
bleibt genau beim Anruf und der Probe.

Der alte Name kracht jetzt, statt still "normal" zu liefern.

pruef-push-eilig.mjs (neu, 13 Pruefungen): die Entscheidung je Art
unabhaengig notiert statt aus der Artenliste abgelesen, jede Art
muss eine Entscheidung haben, und der Kopf wird durch
benachrichtige() hindurch an einem nachgebauten Push-Dienst
gemessen. Gegenprobe gelaufen: ohne das Feld meldet sie
"chat_nachricht geht mit Urgency: normal hinaus", 4 Fehler.
Die Uhr ist dort eine Eingabe, keine Annahme -- die Zeitzone wird so
gewaehlt, dass der Lauf immer auf 12 Uhr faellt, sonst waere die
Pruefung nachts rot ohne Befund.

pruef-anruf-klingelt 25 -> 26. Dabei aufgefallen: Eine ihrer
Pruefungen war gruen, obwohl die Zeile aus dem Code verschwunden war
-- sie stand nur noch in einem Kommentar, der die alte Fassung
zitiert. Quelltextpruefungen lesen dort jetzt ohne Kommentare.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-03 12:58:01 +02:00
co-authored by Claude Opus 5
parent db8dd2fbea
commit 73f9b24793
4 changed files with 420 additions and 36 deletions
+69 -19
View File
@@ -113,19 +113,19 @@ const jetzt = () => new Date().toISOString();
export const ARTEN = [
{ schluessel: "aufgabe_faellig", name: "Aufgabe wird morgen fällig", vorgabe: true },
{ schluessel: "aufgabe_ueberfaellig", name: "Aufgabe ist überfällig", vorgabe: true },
{ schluessel: "termin_gleich", name: "Termin in einer Stunde", vorgabe: true },
{ schluessel: "termin_gleich", eilig: true, name: "Termin in einer Stunde", vorgabe: true },
/* DER WECKER (07.09.2026). Bewusst eine EIGENE Art neben
"termin_gleich": Die eine ist eine Vorgabe des Hauses fuer jeden
Termin, der andere ist eine Entscheidung DIESER Person fuer DIESEN
Termin. Wer die feste Stunden-Erinnerung abschaltet, will damit
nicht seine selbst gesetzten Wecker verlieren. */
{ schluessel: "termin_wecker", name: "Selbst gesetzter Wecker", vorgabe: true },
{ schluessel: "termin_wecker", eilig: true, name: "Selbst gesetzter Wecker", vorgabe: true },
{ schluessel: "protokoll_fehlt", name: "Gespräch ohne Protokoll", vorgabe: true },
{ schluessel: "followup", name: "Follow-up in der Pipeline fällig", vorgabe: true },
/* Chat (06.09.2026). Steht bewusst VOR der Probe: Es ist die Art,
die am haeufigsten kommt, und wer die Liste ueberfliegt, sucht
zuerst sie. */
{ schluessel: "chat_nachricht", name: "Neue Nachricht im Chat", vorgabe: true },
{ schluessel: "chat_nachricht", eilig: true, name: "Neue Nachricht im Chat", vorgabe: true },
/* ---- ANGESPROCHEN WERDEN IST EINE EIGENE ART (20.09.2026) ------
Filipe: "ich will das man die leute mit @ markieren kann im chat."
@@ -144,7 +144,7 @@ export const ARTEN = [
KEINE Ausnahme von der Ruhezeit: Ein Anruf wartet auf eine
Antwort, ein "@Anna" nicht. Wer um halb vier angesprochen wird,
liest es um sieben -- das reicht. */
{ schluessel: "chat_erwaehnung", name: "Jemand erwähnt dich im Chat (@)", vorgabe: true },
{ schluessel: "chat_erwaehnung", eilig: true, name: "Jemand erwähnt dich im Chat (@)", vorgabe: true },
/* ---- BEWERBUNGEN (23.09.2026) -----------------------------------
@@ -189,7 +189,7 @@ export const ARTEN = [
NICHT DRINGEND: Ein kaputter Knopf weckt niemanden um drei Uhr
nachts; die Ruhezeit gilt. */
{ schluessel: "support",
{ schluessel: "support", eilig: true,
name: "Support: neue Meldung oder Antwort darauf", vorgabe: true },
/* ---- MANAGER-ZIELE (02.10.2026) ---------------------------------
@@ -224,7 +224,7 @@ export const ARTEN = [
Eine eigene Art und nicht "termin_gleich": Das eine ist ein
Kalendereintrag, das andere ein Stream, der JETZT laeuft. Wer das
eine stumm stellt, meint selten das andere. */
{ schluessel: "dogfather_live", name: "DogFather ist live", vorgabe: true,
{ schluessel: "dogfather_live", eilig: true, name: "DogFather ist live", vorgabe: true,
/* „spaet", nicht „immer": Ein Stream, der um 22:05 anfaengt, ist um
22:05 interessant. Mit der alten Regel waere er stumm geblieben,
und die Meldung, die Filipe ausdruecklich bestellt hat („die
@@ -257,7 +257,7 @@ export const ARTEN = [
`ruhe: "spaet"` aus demselben Grund wie oben: Eine Reaction um
22:30 ist der Normalfall, keine Ausnahme. */
{ schluessel: "reaktion_live",
{ schluessel: "reaktion_live", eilig: true,
name: "Die Reaction geht auf Sendung", vorgabe: true, ruhe: "spaet" },
/* ---- ANRUFE SIND EINE EIGENE ART (18.09.2026) ------------------
@@ -283,7 +283,7 @@ export const ARTEN = [
ist kein Telefon. Wer nachts nicht gestoert werden will, schaltet
jetzt diese Art ab -- eine Entscheidung, die man selbst trifft,
statt einer Regel, die man nicht kennt. */
{ schluessel: "anruf", name: "Jemand ruft an", vorgabe: true, ruhe: "nie" },
{ schluessel: "anruf", eilig: true, name: "Jemand ruft an", vorgabe: true, ruhe: "nie" },
/* DER TAGESRUF (08.09.2026, screen10). Filipe: "eine taetige meldung
einmal am tag zu aktivieren wenn noch aufgaben auf sind."
@@ -323,10 +323,10 @@ export const ARTEN = [
IM TEXT STEHT NIE, WORUM ES GEHT. Eine Benachrichtigung erscheint
auf einem Sperrbildschirm, den auch andere sehen. */
{ schluessel: "hilfe", name: "Vertrauliche Meldung: neu oder beantwortet", vorgabe: true },
{ schluessel: "hilfe", eilig: true, name: "Vertrauliche Meldung: neu oder beantwortet", vorgabe: true },
/* Die Probe beantwortet „kommt ueberhaupt etwas an?" -- und die
stellt man sich auch um 23 Uhr. */
{ schluessel: "test", name: "Probe-Benachrichtigung", vorgabe: true, ruhe: "nie" },
{ schluessel: "test", eilig: true, name: "Probe-Benachrichtigung", vorgabe: true, ruhe: "nie" },
];
const ARTEN_SCHLUESSEL = new Set(ARTEN.map((a) => a.schluessel));
@@ -382,6 +382,49 @@ export function ruheRegel(art) {
return ARTEN.find((a) => a.schluessel === art)?.ruhe || "immer";
}
/** Ist diese Art EILIG -- also wertlos, wenn sie zu spaet kommt?
*
* WARUM DAS EINE EIGENE FRAGE IST (03.10.2026, nach Dienes Meldung).
*
* Diene hat gemeldet: "Die Benachrichtigungen werden nicht angezeigt,
* wenn neue Nachrichten reinkommen. Erst, wenn man die App oeffnet."
* Das war kein Fehler an ihrem Geraet und keiner am Service Worker --
* es war der Kopf `Urgency`, den wir mitschicken.
*
* Beide Push-Dienste behandeln "normal" ausdruecklich als "darf
* warten": Android/FCM haelt solche Meldungen zurueck, solange das
* Telefon doest, und stellt sie zu, wenn es aufwacht -- typischerweise,
* wenn jemand das Geraet entsperrt oder die App oeffnet. Apple/APNs
* nennt es "verzoegert, gebuendelt oder gedrosselt". Beschrieben ist
* damit genau Dienes Satz, Wort fuer Wort.
*
* Bis hierher hing die Dringlichkeit an der RUHEREGEL:
*
* const dringend = regel === "nie";
*
* Daneben stand die Begruendung, eine zweite Bedingung sei die, "die
* beim naechsten Nachschaerfen auseinanderlaeuft". Die Vorhersage war
* richtig, nur in die andere Richtung: Die zwei Fragen haben eben
* NICHT dieselbe Antwort.
*
* "Darf das nachts stoeren?" -> Chat: nein.
* "Darf das warten?" -> Chat: auch nein.
*
* Eine Nachricht um drei Uhr nachts soll schweigen -- um zwei Uhr
* nachmittags aber nicht vierzig Minuten liegen bleiben. Deshalb zwei
* Felder, aber in DERSELBEN Liste: auseinanderlaufen koennen sie nur,
* wenn sie an zwei Orten stehen.
*
* MASSSTAB: eilig ist, was durch Warten WERTLOS wird -- ein Anruf, ein
* Wecker, eine Nachricht, auf die jemand wartet, ein "wir sind live".
* Nicht eilig ist, was auch spaeter noch stimmt -- eine Aufgabe, die
* morgen faellig ist, der Tagesruf, eine Bewerbung. Waere alles eilig,
* waere nichts mehr eilig: Die Geraete duerfen uns die Dringlichkeit
* glauben, sonst drosseln sie uns -- und der Akku zahlt es mit. */
export function eiligRegel(art) {
return ARTEN.find((a) => a.schluessel === art)?.eilig === true;
}
/** Ist gerade Ruhezeit -- fuer diese Regel?
*
* `regel` steht hinten und hat eine Vorgabe, damit vorhandene
@@ -598,7 +641,21 @@ export async function benachrichtige(personId, art, { titel, text, ziel, merkmal
schlafendes Geraet dafuer aufwacht. Das gilt fuer den Anruf und
die Probe -- nicht fuer eine Einladung: Die soll ankommen, wenn
das Geraet ohnehin wach wird, und keinen Akku dafuer verbrennen. */
const dringend = regel === "nie";
const eilig = eiligRegel(art);
/* KURZLEBIG IST NICHT DASSELBE WIE EILIG -- und das Zusammenlegen
waere hier der teure Fehler gewesen (03.10.2026).
Bis heute steuerte EIN Schalter beides: `dringend` hat das Geraet
geweckt UND die Haltbarkeit auf 150 Sekunden gesetzt. Haette ich
den Chat einfach auf `dringend` gestellt, waere aus "kommt vierzig
Minuten zu spaet" ein "kommt gar nicht mehr" geworden: Wer sein
Telefon drei Minuten aus hat, haette die Nachricht dann verloren.
Schlimmer als der Fehler, den ich beheben wollte.
Kurz haltbar bleibt deshalb genau das, was von selbst vorbei ist
-- der Anruf, der 120 Sekunden klingelt, und die Probe, bei der
jemand davorsteht. Das ist unveraendert `ruhe: "nie"`. */
const kurzlebig = regel === "nie";
/* Schon einmal geschickt? */
if (merkmal) {
@@ -633,14 +690,7 @@ export async function benachrichtige(personId, art, { titel, text, ziel, merkmal
let raus = 0;
for (const a of anmeldungen) {
/* EIN ANRUF IST DRINGEND, ALLES ANDERE NICHT (19.09.2026).
`dringend` ist hier dasselbe `art === "anruf"`, das zwanzig
Zeilen weiter oben schon ueber die Ruhezeit entscheidet -- die
Frage „darf das nachts stoeren?" und „darf das warten?" haben
dieselbe Antwort. Eine zweite Bedingung waere die, die beim
naechsten Nachschaerfen auseinanderlaeuft. */
const e = await schicken(a, inhalt, paar, ABSENDER, { dringend });
const e = await schicken(a, inhalt, paar, ABSENDER, { eilig, kurzlebig });
if (e.ok) {
raus++;
db().prepare("UPDATE push_anmeldungen SET zuletzt_ok = ?, fehler = 0 WHERE endpunkt = ?")