Files
dogfather-universe/server/workspace-bewerbung-melden.js
T
DogFatherGitandClaude Opus 5 1f2a4d335e Eine Bewerbung, die niemand sieht, ist keine
Beim Weiterarbeiten am Vorlagenbrett nachgemessen und gefunden:
`benachrichtige` kam in workspace-zuteilung.js KEIN EINZIGES MAL vor,
in workspace-vorlagen.js auch nicht.

Beide Bewerbungswege waren gebaut, beide funktionierten -- und beide
waren stumm:

  * Bewirbt sich Frida, erfaehrt DogFather es nur, wenn er von sich
    aus das Brett aufmacht.
  * Antwortet er, erfaehrt Frida es nur, wenn SIE von sich aus
    nachsieht.

Das ist keine Kleinigkeit, das ist die Funktion. Wer sich bewirbt,
wartet -- und Warten ohne Rueckmeldung fuehlt sich nach zwei Tagen an
wie "interessiert keinen". Genau das soll eine Bewerbung verhindern.

WER ES ERFAEHRT -- ABGELEITET, NICHT AUFGEZAEHLT
------------------------------------------------
Die naheliegende Zeile waere `rolle IN ('admin','hand')` gewesen; so
steht sie in workspace-hilfe.js. Das ist eine Abschrift, und
Abschriften altern: Kaeme morgen eine Rolle dazu, die entscheiden
darf, bekaeme sie keine einzige Meldung -- und niemand merkte es, weil
ja alles funktioniert.

Gefragt wird deshalb die Regel selbst (entscheidetUeberAufgaben),
Person fuer Person. Und zusaetzlich darfSchreibenMit: Wer den Bewerber
gar nicht sehen darf, bekommt auch keine Meldung ueber ihn. Das ist
keine Vorsicht um ihrer selbst willen -- ohne diese Zeile erfuehre die
Agentur ueber eine Push-Nachricht, dass es Team Dogi ueberhaupt gibt.

Gemessen: Die Bewerbung eines Modis erreicht genau zwei Leute
(admin, hand) von sechs Aktiven. Nicht die linke Hand (sie entscheidet
hier nicht mit), niemand aus dem anderen Haus, und nicht der Bewerber
selbst.

ZWEI SCHALTER, ZWEI ENTSCHEIDUNGEN
----------------------------------
"bewerbung_neu" trifft den, der antwortet -- an einem lebhaften Tag
mehrfach, das kann man stumm stellen wollen. "bewerbung_antwort"
trifft den, der wartet; sie kommt einmal, und niemand will sie stumm
stellen. Eine gemeinsame Art hiesse: beides zusammen abschalten oder
beides zusammen ertragen. Dieselbe Ueberlegung wie beim Chat
(Nachricht / Erwaehnung).

Beide von sich aus an. Keine Ausnahme von der Ruhezeit: Eine Bewerbung
wartet, ein Anruf nicht.

DIE NOTIZ STEHT IN DER MELDUNG
------------------------------
Filipe hat sie ausdruecklich verlangt ("mit einem text als notiz").
Sie erst zu verlangen und dann an genau der Stelle zu verschweigen, an
der man sie liest, waere die halbe Funktion. Und das Ergebnis steht im
TITEL -- "angenommen" oder "diesmal nicht" -- damit man es lesen kann,
ohne zu oeffnen. Auch die gute Nachricht.

Der Wortlaut steht in zwei reinen Funktionen (bewerbungText,
antwortText), exportiert, damit eine Pruefung sie lesen kann, ohne
einen Push-Dienst nachzubauen. Genau an so einer Stelle steckte am
18.09. der Fehler "Nachricht von [object Object]", der von aussen
nicht messbar war.

EINE STELLE FUER BEIDE WEGE
---------------------------
workspace-bewerbung-melden.js. Zwei Fassungen waeren zwei
Gelegenheiten, dass eine davon die Ruhezeit, die Abschaltbarkeit oder
die Haeusertrennung vergisst -- und dieselbe Person laese zweimal
etwas Verschiedenes ueber denselben Vorgang.

Die Meldung wird NICHT abgewartet (`void`): Ob sie durchgeht, haengt
am Push-Dienst, an der Ruhezeit und an den Einstellungen des
Empfaengers. Nichts davon darf entscheiden, ob die Bewerbung
gespeichert ist -- die ist es laengst.

NOCH EINE ROTE PRUEFUNG, DIE NIEMAND GESEHEN HAT
-------------------------------------------------
pruef-push-ziel meldete: "aber nicht auf eine Seite, die es fuer ihn
nicht gibt (/workspace/calls.html)". Das sah aus wie ein Befund und
war eine erfuellte Bestellung -- Filipe hatte am 22.09. genau das
Gegenteil bestellt ("jeder der einen kalender hat soll auch sowas
haben"). Nachgemessen: Modi, rechte und linke Hand haben je eine
Calls-Kachel.

Die Pruefung steht jetzt andersherum: Die Calls-Seite MUSS stehen
bleiben. Dieselbe Zeile schuetzt damit das, was sie vorher verboten
hat -- und wird rot, wenn die Kachel je wieder verschwindet. Das
Umlenken selbst bleibt geprueft (Scouting, zweimal).

Das ist die DRITTE stille rote Pruefung an einem Tag (nach
pruef-modi-wortleck und pruef-zuteilung). Die Frage an Filipe, ob ein
naechtlicher Lauf sie selbst anstossen soll, steht in der Vault-Notiz
und wird nicht von mir allein entschieden.

GEPRUEFT
--------
pruef-modi-katalog: 116 Pruefungen, 0 Fehler (vorher 95).
  Neu: die beiden Schalter, wer es erfaehrt (samt Gegenprobe, dass es
  nicht einfach alle sind: 2 von 6), und der Wortlaut an acht Proben.
  Dabei war meine eigene erste Messung falsch -- sie erwartete eine
  Kuerzung bei 50 Zeichen, die nur gilt, wenn eine Notiz danebensteht.
  Steht als Begruendung in der Pruefung.
pruef-push-ziel: 11 von 11 (vorher 1 Fehler).
pruef-zuteilung, pruef-push, pruef-push-weg: gruen.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-23 11:16:30 +02:00

186 lines
7.7 KiB
JavaScript

/* =====================================================================
EINE BEWERBUNG, DIE NIEMAND SIEHT, IST KEINE (23.09.2026)
=====================================================================
Filipe, 22.09.2026: „die option dass die modis sich für aufgaben
bewerben können."
Filipe, 23.09.2026: „nur dogfather und die rechte hand sollen
annehmen oder ablehnen können, mit einem text als notiz."
---------------------------------------------------------------------
WAS GEFEHLT HAT -- und es ist der ganze Grund für diese Datei
Beide Wege waren gebaut und beide funktionierten. Nur: Wenn Frida
sich bewirbt, erfährt DogFather das ausschließlich dann, wenn er von
sich aus das Brett aufmacht. Und wenn er antwortet, erfährt Frida es
ausschließlich dann, wenn SIE von sich aus nachsieht.
Das ist keine Kleinigkeit, das ist die Funktion. Wer sich bewirbt,
wartet -- und Warten ohne Rückmeldung fühlt sich nach zwei Tagen an
wie „interessiert keinen". Genau das Gefühl soll eine Bewerbung
verhindern.
Nachgemessen, bevor gebaut wurde: In workspace-zuteilung.js kam
`benachrichtige` kein einziges Mal vor, in workspace-vorlagen.js
auch nicht. Es war keine vergessene Zeile, es war ein fehlendes
Stück.
---------------------------------------------------------------------
WARUM EINE EIGENE DATEI
Es gibt ZWEI Bewerbungswege -- auf eine Aufgabe, die es schon gibt
(workspace-zuteilung.js), und auf eine Vorlage, aus der erst eine
wird (workspace-vorlagen.js). Beide sollen dasselbe melden, sonst
liest dieselbe Person zweimal etwas Verschiedenes über denselben
Vorgang.
Zwei Fassungen wären zwei Gelegenheiten, dass eine davon beim
nächsten Umbau die Ruhezeit, die Abschaltbarkeit oder die
Häusertrennung vergisst.
---------------------------------------------------------------------
WER ERFÄHRT ES -- ABGELEITET, NICHT AUFGEZÄHLT
Die naheliegende Zeile wäre `rolle IN ('admin','hand')` gewesen; so
steht sie in workspace-hilfe.js. Sie ist eine Abschrift, und
Abschriften altern: Käme morgen eine Rolle dazu, die entscheiden
darf, bekäme sie keine einzige Meldung -- und niemand merkte es,
weil ja alles funktioniert.
Gefragt wird deshalb die Regel selbst (`entscheidetUeberAufgaben`),
Person für Person. Und zusätzlich `darfSchreibenMit`: Wer den
Bewerber gar nicht sehen darf, bekommt auch keine Meldung über ihn.
Das ist keine Vorsicht um ihrer selbst willen -- ohne diese Zeile
erführe die Agentur über eine Push-Nachricht, dass es Team Dogi
gibt.
===================================================================== */
import {
db, entscheidetUeberAufgaben, darfSchreibenMit,
} from "./workspace.js";
/** Wer über die Bewerbung DIESER Person entscheiden kann und darf.
*
* ZWEI BEDINGUNGEN, ZWEI GRÜNDE:
* entscheidetUeberAufgaben -- er darf antworten
* darfSchreibenMit -- er darf den Bewerber überhaupt sehen
*
* Der Bewerber selbst fällt heraus: Wer entscheiden darf, bewirbt
* sich nicht (die Routen lehnen das ab) -- aber falls doch einmal,
* wäre eine Meldung an sich selbst Unsinn.
*/
export function entscheiderFuer(bewerber) {
try {
const alle = db().prepare(
"SELECT id, rolle, name FROM personen WHERE aktiv = 1").all();
return alle
.filter((p) => p.id !== bewerber.id)
.filter((p) => entscheidetUeberAufgaben(p))
.filter((p) => darfSchreibenMit(p, bewerber.id));
} catch (fehler) {
console.error("[bewerbung] Entscheider nicht ermittelbar:", fehler?.message);
return [];
}
}
/** Träge geladen -- wie überall im Haus.
*
* Der Bewerbungsweg soll auch dann laufen, wenn am Push-Teil etwas
* klemmt. Eine Bewerbung, die wegen eines Schlüsselproblems gar nicht
* erst gespeichert wird, wäre der schlechtere Tausch.
*/
async function schicken(personId, art, inhalt) {
try {
const { benachrichtige } = await import("./workspace-push.js");
await benachrichtige(personId, art, inhalt);
} catch (fehler) {
console.error("[bewerbung] Benachrichtigung:", fehler?.message);
}
}
/** Kurz halten, aber nicht abschneiden mitten im Wort.
*
* Eine Benachrichtigung steht auf einem Sperrbildschirm; was dort
* nicht hinpasst, verschluckt das Gerät ohnehin. Ein hartes `slice`
* endet dabei gern mitten im Wort, und das liest sich wie ein Fehler.
*/
function kurz(text, max = 90) {
const t = String(text || "").trim().replace(/\s+/g, " ");
if (t.length <= max) return t;
const schnitt = t.slice(0, max);
const luecke = schnitt.lastIndexOf(" ");
return (luecke > max * 0.6 ? schnitt.slice(0, luecke) : schnitt) + " …";
}
/* =====================================================================
WAS DRINSTEHT -- als eigene, reine Funktionen
=====================================================================
ALS EIGENE FUNKTIONEN UND EXPORTIERT, damit eine Prüfung sie lesen
kann, ohne einen Push-Dienst nachzubauen. Genau an dieser Stelle
steckte am 18.09.2026 ein Fehler, den Filipe gemeldet hat und der
von außen nicht messbar war: Der Wortlaut entstand tief in einer
Funktion, die nur der Push-Weg aufruft, und lautete am Ende
„Nachricht von [object Object]".
Sie bekommen nichts als Text und geben Text zurück -- keine
Datenbank, kein Netz. Eine Prüfung kann sie deshalb mit einer Zeile
an fünfzehn Proben halten.
===================================================================== */
/**
* „Jemand bewirbt sich" -- für alle, die antworten können.
*
* DER TITEL NENNT DEN MENSCHEN, nicht die Aufgabe. Wer drei Meldungen
* untereinander sieht, will wissen, WER etwas von ihm will; welche
* Aufgabe es war, steht in der Zeile darunter.
*/
export function bewerbungText({ name, titel, wort }) {
return {
titel: `${String(name || "Jemand").trim() || "Jemand"} bewirbt sich`,
/* DAS WORT DES BEWERBERS GEHT MIT, wenn es eines gibt. Es ist oft
die ganze Auskunft („ich habe Freitag Zeit") -- und wer sie
schon auf dem Sperrbildschirm liest, kann entscheiden, ohne die
Seite zu öffnen. */
text: wort ? `${kurz(titel, 60)} — „${kurz(wort, 70)}“` : kurz(titel),
};
}
/**
* „Deine Bewerbung ist beantwortet" -- für den, der gewartet hat.
*
* DIE ANTWORT IST DER WICHTIGERE TEIL. Wer sich bewirbt, wartet; eine
* Absage, die man drei Tage später zufällig entdeckt, ist schlimmer
* als eine sofortige.
*
* ZWEI VERSCHIEDENE TITEL, und das ist Absicht: „angenommen" und
* „diesmal nicht" sagen das Ergebnis, bevor man öffnet. Ein
* gemeinsames „Antwort auf deine Bewerbung" zwänge jedes Mal zum
* Nachsehen -- auch bei der guten Nachricht.
*
* DIE NOTIZ STEHT DRIN -- genau dafür hat Filipe sie verlangt („mit
* einem text als notiz"). Sie erst zu verlangen und dann an der
* Stelle zu verschweigen, an der man sie liest, wäre die halbe
* Funktion.
*/
export function antwortText({ titel, ja, notiz }) {
return {
titel: ja ? "Deine Bewerbung: angenommen" : "Deine Bewerbung: diesmal nicht",
text: notiz ? `${kurz(titel, 50)} — „${kurz(notiz, 80)}“` : kurz(titel),
};
}
/** Schickt „jemand bewirbt sich" an alle, die antworten können.
* Gibt zurück, an wie viele -- die Zahl braucht die Prüfung. */
export async function meldeBewerbung({ bewerber, titel, wort, ziel }) {
const leute = entscheiderFuer(bewerber);
const inhalt = bewerbungText({ name: bewerber.name, titel, wort });
for (const p of leute) await schicken(p.id, "bewerbung_neu", { ...inhalt, ziel });
return leute.length;
}
/** Schickt die Antwort an den, der gewartet hat. */
export async function meldeBewerbungsantwort({ anWen, titel, ja, notiz, ziel }) {
await schicken(anWen, "bewerbung_antwort", { ...antwortText({ titel, ja, notiz }), ziel });
}