Der sechste Bereich, und zwei Pruefungen, die den Lauf lahmgelegt haben

DER AGENTUR-BEREICH (Stufe 4 des Plans)
Kernprinzip 04 des Konzepts lautet woertlich "Die Agentur bleibt
angebunden" -- und dafuer gab es bis heute nichts. Fuenf Bereiche
standen, der sechste fehlte vollstaendig. Damit stand nirgends, welche
Kampagne laeuft, welche Schulung ansteht und wie ein Anliegen an die
Agentur ausgegangen ist. Das lief ueber private Nachrichten: nicht
auffindbar, nicht nachvollziehbar, beim naechsten Mal von vorn.

Vier Arten, und die vierte ist der Punkt: kampagne, schulung, anliegen
und ZUSTAENDIGKEIT. Die letzte ist keine Verlegenheit, sondern das, was
das Konzept ausdruecklich fordert -- Betreuung und Umsetzung sind unsere
Seite, offizielle Wege sind Agenturseite. Solange das nur im Kopf steht,
wird es bei jedem Streitfall neu verhandelt.

Der Umbau war das eigentliche Risiko, nicht der Bereich: Ein CHECK
laesst sich in SQLite nicht aendern, die Tabelle muss neu gebaut werden
-- und dabei werden ALLE vorhandenen Eintraege umgeschrieben. Deshalb
geht pruef-agentur.mjs (31 Pruefungen) den Weg wirklich: Sie baut eine
Datenbank im ALTEN Stand nach, fuellt sie mit allen fuenf Bereichen,
laesst die echte Anwendung darueberlaufen und zaehlt danach jede Zeile
UND jedes Feld nach -- auch die seltenen Spalten (hook, creator_extern),
die man beim Abschreiben des Bauplans vergisst. Wichtigste Gegenprobe:
Der CHECK muss danach noch BEISSEN. Eine Umstellung, die nebenbei die
Schranke entfernt, sieht aus wie ein Erfolg und ist der schlimmere
Ausgang.

Nebenbei drei abgeschriebene Bereichslisten beseitigt (Wochenbericht,
Suche, Report-Ziele). Im Wochenbericht waere der neue Bereich sonst
schlicht nicht vorgekommen -- ohne Fehler, ohne Luecke, einfach nicht da.

ZWEI PRUEFUNGEN, DIE DEN GESAMTLAUF ZUM STILLSTAND BRACHTEN

pruef-call-kategorien.mjs hing DREI STUNDEN. Ursache war ein Zeitzuender
in ihr selbst: Sie legte Calls auf "heute 18:00" und erwartete zwei
davon unter "Heute". Ab 18 Uhr sind die vorbei, der Server schiebt sie
nach "Protokoll fehlt", und die Oberflaeche unterteilt erst ab FUENF
anstehenden. Uebrig blieben vier, die Bloecke verschwanden, und die
Pruefung wartete auf einen Klick auf einen Block, den es nicht gab.
Genau die Sorte Fehler, vor der die Projektnotiz vom 06.09. warnt: Sie
war um 17:59 gruen und um 18:01 rot, ohne dass sich am Code etwas
geaendert hatte. Jetzt liegen alle Zeiten RELATIV zu jetzt, und die
Erwartung wird mit derselben Vorschrift gerechnet, die calls.js benutzt
-- statt Zahlen, die nur zu einer bestimmten Tageszeit stimmen.

Und der Grund, warum daraus ein Stillstand statt eines Fehlers wurde:
index.js setzt bewusst zwei Auffangnetze (uncaughtException,
unhandledRejection). Fuer den Betrieb richtig -- ein Fehler darf die
Website nicht offline nehmen. Fuer eine Pruefung fatal: Sie importiert
index.js in denselben Prozess und erbt die Netze. Ihr eigener Absturz
wird dann nur protokolliert, und der Express-Server haelt den Prozess
danach ewig am Leben. Kein Fehler, kein Ergebnis, kein Ende.

Deshalb zwei Abhilfen, die zusammengehoeren:
  * server/helfer-notbremse.mjs -- haengt eigene Zuhoerer DANEBEN
    (process.on ergaenzt, es ersetzt nicht) und beendet den Lauf mit
    Fehler; dazu ein Wecker. In alle 42 Browserpruefungen eingebaut.
    Der Betrieb bleibt unveraendert.
  * tools/alles-pruefen.mjs gibt jeder Datei eine Frist von 600 s. Wer
    sie reisst, wird abgebrochen und ZAEHLT ALS FEHLGESCHLAGEN, mit der
    letzten Ausgabe davor als Beleg. Einzelne Dateien nachzuruesten hilft
    nur bis zur naechsten ohne Notbremse -- deshalb sitzt sie dort, wo
    sie fuer alle gilt, auch fuer die, die es noch nicht gibt.

AUSSERDEM: pruef-breiten.mjs pruefte 16 Seiten aus einer Liste von Hand,
chat.html und leistung.html fehlten darin. Der Chat war bei keiner
einzigen der neun Breiten je gemessen worden. Wie bei pruef-handy kommt
die Liste jetzt aus dem Verzeichnis.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-06 22:06:49 +02:00
co-authored by Claude Opus 5
parent 80a8aac774
commit bede91921d
71 changed files with 1413 additions and 224 deletions
+75
View File
@@ -0,0 +1,75 @@
/* DIE NOTBREMSE FÜR PRÜFUNGEN
---------------------------------------------------------------------
DER FEHLER, DEN DAS VERHINDERT (06.09.2026, drei Stunden gekostet)
index.js setzt am Ende bewusst zwei Auffangnetze:
process.on("uncaughtException", (f) => console.error(…));
process.on("unhandledRejection", (g) => console.error(…));
Für den echten Betrieb ist das genau richtig: Ein Fehler in einer
Nebensache darf die Website nicht offline nehmen.
Für eine PRÜFUNG ist es das Gegenteil. Eine Prüfung importiert
index.js in denselben Prozess -- und erbt damit die Netze. Wenn dann
ihr eigener Ablauf abstürzt (ein Playwright-Klick auf ein Element,
das es nicht gibt), wird der Absturz nur PROTOKOLLIERT. Danach
passiert nichts mehr: Das Skript ist zu Ende, aber der Express-Server
hält den Prozess am Leben. Kein Fehler, kein Ergebnis, kein Ende.
Genau so blieb pruef-call-kategorien.mjs drei Stunden stehen -- und
mit ihr der gesamte Regressionslauf, der auf sie wartete. Von aussen
sieht "läuft noch" genauso aus wie "hängt für immer".
---------------------------------------------------------------------
WAS DIESE DATEI TUT
Sie hängt ZWEI eigene Zuhörer daneben (process.on ergänzt, es
ersetzt nicht -- die Meldung von index.js bleibt also erhalten) und
beendet den Lauf mit einem Fehler. Und sie stellt einen Wecker: Wer
nach der Frist noch läuft, wartet nicht mehr, er hängt.
Beides greift NUR in Prüfungen. Der Betrieb importiert diese Datei
nicht und behält seine Netze unverändert.
---------------------------------------------------------------------
BENUTZUNG — nach dem Import von index.js, vor dem ersten Browser:
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
notbremse(300_000, "die Kategorienpruefung");
*/
/** @param {number} fristMs Nach dieser Zeit gilt der Lauf als haengend.
* @param {string} wer Name für die Meldung. */
export function notbremse(fristMs = 300_000, wer = "diese Pruefung") {
let schonAus = false;
const raus = (art, was) => {
if (schonAus) return;
schonAus = true;
console.error(`\n FEHL ${wer}: ${art}`);
console.error(` ${String(was?.stack || was).split("\n").slice(0, 6).join("\n ")}`);
console.error(`\n Abgebrochen. Ohne diese Notbremse wuerde der Prozess`);
console.error(` weiterlaufen, weil der Server ihn am Leben haelt --`);
console.error(` ohne Fehler, ohne Ergebnis, ohne Ende.\n`);
process.exit(1);
};
process.on("uncaughtException", (f) => raus("unbehandelter Fehler", f));
process.on("unhandledRejection", (g) => raus("unbehandeltes Versprechen", g));
/* unref(): Der Wecker allein soll den Prozess NICHT am Leben halten.
Sonst haette ausgerechnet die Notbremse dafuer gesorgt, dass eine
fertige Pruefung noch fuenf Minuten herumsteht. */
const wecker = setTimeout(() => {
console.error(`\n FEHL ${wer} HING: nach ${Math.round(fristMs / 1000)} s war sie `
+ `noch nicht fertig.`);
console.error(` Haeufigste Ursachen: ein Warten auf ein Element, das nie kommt,`);
console.error(` oder eine Verbindung, die nie endet (Ereignisstrom).\n`);
process.exit(1);
}, fristMs);
wecker.unref?.();
return () => clearTimeout(wecker);
}
+7
View File
@@ -28,6 +28,7 @@ import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-abopt-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
import { notbremse } from "./helfer-notbremse.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4297, "die Optikpruefung des Abbrechens");
@@ -39,6 +40,12 @@ const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-abbrechen-optik");
await new Promise((r) => setTimeout(r, 900));
const BASIS = "http://127.0.0.1:4297";
+474
View File
@@ -0,0 +1,474 @@
/* DER SECHSTE BEREICH — "Die Agentur bleibt angebunden"
Kernprinzip 04 des Konzepts, und bis zum 06.09.2026 gab es dafuer
nichts: Fuenf Bereiche standen (LIVE, Content, Technik, Community,
Schutz), der sechste fehlte vollstaendig.
---------------------------------------------------------------------
DAS EIGENTLICHE RISIKO IST NICHT DER BEREICH, SONDERN DER UMBAU
Der erlaubte Wertebereich einer Spalte steckt in SQLite in einem
CHECK, und ein CHECK laesst sich nicht aendern -- die Tabelle muss
neu gebaut werden. Dabei werden ALLE vorhandenen Eintraege
umgeschrieben: LIVE-Auswertungen, Content-Ideen, Technikprobleme,
Community-Konflikte, Vorfaelle. Wer dort eine Spalte vergisst,
verliert ihren Inhalt still.
Und das ist ein Weg, den man nicht zurueckdrehen kann. Deshalb wird
er hier nicht "im Prinzip" geprueft, sondern WIRKLICH GEGANGEN: Die
Pruefung baut eine Datenbank im ALTEN Stand nach, fuellt sie, laesst
die echte Anwendung darueberlaufen und zaehlt danach jede Zeile und
jedes Feld nach.
GEGENPROBEN durchgehend -- vor allem die eine, auf die es ankommt:
Der CHECK muss danach immer noch BEISSEN. Eine Umstellung, die
nebenbei die Schranke entfernt, sieht aus wie ein Erfolg und ist der
schlimmere Ausgang.
===================================================================== */
import { mkdtempSync, rmSync, appendFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";
import { DatabaseSync } from "node:sqlite";
import { scryptSync, randomBytes } from "node:crypto";
import { heuteLokal } from "./helfer-zeit.mjs";
const HIER = dirname(fileURLToPath(import.meta.url));
const { portMussFreiSein } = await import("./helfer-port.mjs");
const PORT = 4341;
await portMussFreiSein(PORT, "die Agentur-Pruefung");
const BASIS = `http://127.0.0.1:${PORT}`;
const ordner = mkdtempSync(join(tmpdir(), "ws-agentur-"));
const DB = join(ordner, "workspace.db");
const PROTOKOLL = join(process.cwd(), "pruef-agentur-lauf.txt");
try { rmSync(PROTOKOLL, { force: true }); } catch { /* gab es noch nicht */ }
const melde = (z) => {
console.log(z);
try { appendFileSync(PROTOKOLL, z + "\n"); } catch { /* Zugabe */ }
};
let fehler = 0;
let gemacht = 0;
const ok = (b, t) => { gemacht++; melde((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
/* ---------------------------------------------------------------------
Die Anwendung als eigener Prozess -- weil sie ZWEIMAL starten muss:
einmal, um die Datenbank anzulegen, und einmal nach dem Zurueckbau,
damit die Wanderung wirklich laeuft. In einem Prozess ginge das
nicht: Ein Modul wird nur einmal geladen.
--------------------------------------------------------------------- */
let laufend = null;
async function serverStarten() {
const kind = spawn(process.execPath, [join(HIER, "index.js")], {
env: {
...process.env,
WORKSPACE_DB: DB,
PORT: String(PORT),
SITE_ACCESS_SECRET: "lokaler-test",
SITE_PUBLIC_LAUNCH_AT: "2020-01-01T00:00:00+01:00",
},
stdio: ["ignore", "pipe", "pipe"],
});
let aus = "";
kind.stdout.on("data", (d) => { aus += d; });
kind.stderr.on("data", (d) => { aus += d; });
for (let i = 0; i < 80; i++) {
await new Promise((r) => setTimeout(r, 250));
if (kind.exitCode !== null) break;
try {
const a = await fetch(BASIS + "/workspace/", { redirect: "manual" });
if (a.status < 500) { laufend = kind; return { kind, aus: () => aus }; }
} catch { /* noch nicht oben */ }
}
throw new Error("Server startete nicht:\n" + aus.slice(-1200));
}
async function serverStoppen() {
if (!laufend) return;
laufend.kill();
laufend = null;
/* Warten, bis der Port wirklich frei ist -- sonst startet der zweite
Lauf gegen einen halb geschlossenen Anschluss und misst Unsinn. */
for (let i = 0; i < 40; i++) {
await new Promise((r) => setTimeout(r, 150));
try { await fetch(BASIS + "/workspace/", { redirect: "manual" }); } catch { return; }
}
}
/* =======================================================================
1. Erster Start: die Datenbank entsteht
======================================================================= */
melde("=== Die Datenbank anlegen ===");
{
const { aus } = await serverStarten();
ok(true, "die Anwendung ist oben und hat das Schema angelegt");
void aus;
}
const jetzt = new Date().toISOString();
/* ORTSZEIT fuer das Tagesdatum, nicht der Schnitt aus dem UTC-Stempel.
`jetzt.slice(0, 10)` liegt zwischen Mitternacht und zwei Uhr einen Tag
daneben -- die Pruefung waere dann nachts rot, ohne dass sich etwas
geaendert haette. Gefunden von pruef-struktur, das genau danach sucht. */
const heute = heuteLokal();
function anlegen(d, name, rolle, code) {
const salz = randomBytes(16).toString("hex");
const hash = scryptSync(code, salz, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
.run(name, rolle, hash, salz, 32768, jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
}
let idDogi, idLuna;
{
const d = new DatabaseSync(DB);
idDogi = anlegen(d, "Filipe", "admin", "CODE-DOGI-0001");
idLuna = anlegen(d, "Luna", "creator", "CODE-CREA-0001");
d.close();
}
/* =======================================================================
2. Die Datenbank auf den ALTEN Stand zurueckbauen
=======================================================================
Genau so sieht sie auf dem Server aus, bevor die Wanderung laeuft.
Ohne diesen Schritt pruefte die ganze Datei nur eine frisch angelegte
Tabelle -- und damit ausgerechnet den Fall, den es in der Wirklichkeit
nicht gibt. */
melde("\n=== Zurueck auf den alten Stand (fuenf Bereiche) ===");
await serverStoppen();
const ALTE_EINTRAEGE = [
["live", "auswertung", "Auswertung Montag", "Der Hook saß, die Mitte hing.", 4, "hoch"],
["content", "idee", "Serie: Basteln mit Kindern", "Fünf Teile.", null, "mittel"],
["technik", "problem", "Ton bricht ab", "Nur bei OBS 30.", null, "hoch"],
["community", "konflikt", "Streit in den Kommentaren", "Zwei Zuschauer.", null, "niedrig"],
["schutz", "vorfall", "Meldung wegen Sprache", "Geklärt.", null, "hoch"],
];
{
const d = new DatabaseSync(DB);
d.exec("PRAGMA foreign_keys = OFF");
d.exec("DROP TABLE IF EXISTS eintraege");
/* WOERTLICH der alte Bauplan, mit allen Spalten, die zu diesem
Zeitpunkt schon da waren. */
d.exec(`
CREATE TABLE eintraege (
id INTEGER PRIMARY KEY AUTOINCREMENT,
bereich TEXT NOT NULL
CHECK (bereich IN ('live','content','technik','community','schutz')),
art TEXT NOT NULL,
titel TEXT NOT NULL,
text TEXT,
datum TEXT NOT NULL,
bewertung INTEGER CHECK (bewertung IS NULL OR (bewertung BETWEEN 1 AND 5)),
dringlichkeit TEXT NOT NULL DEFAULT 'mittel'
CHECK (dringlichkeit IN ('hoch','mittel','niedrig')),
status TEXT NOT NULL DEFAULT 'offen'
CHECK (status IN ('offen','erledigt')),
creator_id INTEGER REFERENCES personen(id) ON DELETE SET NULL,
erstellt TEXT NOT NULL,
erstellt_von INTEGER REFERENCES personen(id) ON DELETE SET NULL,
geaendert TEXT,
hook TEXT,
format TEXT,
saeule_id INTEGER,
geplant TEXT,
creator_extern TEXT
);
CREATE INDEX IF NOT EXISTS idx_eintraege_bereich ON eintraege (bereich, creator_id);`);
const setzen = d.prepare(`INSERT INTO eintraege
(bereich, art, titel, text, datum, bewertung, dringlichkeit, status,
creator_id, erstellt, erstellt_von, hook, creator_extern)
VALUES (?,?,?,?,?,?,?, 'offen', ?,?,?,?,?)`);
for (const [b, art, titel, text, bew, dring] of ALTE_EINTRAEGE) {
setzen.run(b, art, titel, text, heute, bew, dring, idLuna, jetzt, idDogi,
b === "content" ? "Der Hook: 'Das habt ihr noch nie gesehen'" : null,
b === "live" ? "Gastauftritt Mia" : null);
}
d.exec("PRAGMA foreign_keys = ON");
const n = d.prepare("SELECT COUNT(*) AS n FROM eintraege").get().n;
ok(n === ALTE_EINTRAEGE.length, `${n} Eintraege im alten Stand angelegt`);
/* GEGENPROBE: Der alte CHECK muss "agentur" WIRKLICH ablehnen --
sonst gaebe es nichts umzustellen, und der Rest dieser Datei
bewiese nichts. */
let abgelehnt = false;
try {
d.prepare(`INSERT INTO eintraege (bereich, art, titel, datum, erstellt)
VALUES ('agentur','kampagne','Darf noch nicht',?,?)`).run(heute, jetzt);
} catch { abgelehnt = true; }
ok(abgelehnt, "im alten Stand wird 'agentur' von der Datenbank abgelehnt");
d.close();
}
/* =======================================================================
3. Die Wanderung -- der Weg, den man nicht zurueckdrehen kann
======================================================================= */
melde("\n=== Die Umstellung ===");
let ausgabe = "";
{
const { aus } = await serverStarten();
await new Promise((r) => setTimeout(r, 600));
ausgabe = aus();
}
ok(/Bereich 'agentur' freigeschaltet/.test(ausgabe),
"die Anwendung meldet die Umstellung"
+ (/agentur/.test(ausgabe) ? "" : ` — Ausgabe: ${ausgabe.slice(-300)}`));
{
const d = new DatabaseSync(DB, { readOnly: true });
const plan = d.prepare(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='eintraege'").get()?.sql || "";
ok(/'agentur'/.test(plan), "der neue Bauplan kennt 'agentur'");
/* KEINE ZEILE DARF FEHLEN -- und keine darf sich VERAENDERT haben.
Die Zeilenzahl allein reichte nicht: Wer beim Umschreiben eine
Spalte vergisst, behaelt alle Zeilen und verliert ihren Inhalt.
Genau das faellt sonst erst Wochen spaeter auf. */
const alle = d.prepare("SELECT * FROM eintraege ORDER BY id").all();
ok(alle.length === ALTE_EINTRAEGE.length,
`alle ${alle.length} Eintraege sind noch da (vorher ${ALTE_EINTRAEGE.length})`);
let abweichungen = [];
ALTE_EINTRAEGE.forEach(([b, art, titel, text, bew, dring], i) => {
const z = alle[i];
if (!z) { abweichungen.push(`${b}: Zeile fehlt`); return; }
if (z.bereich !== b) abweichungen.push(`${b}: bereich = ${z.bereich}`);
if (z.art !== art) abweichungen.push(`${b}: art = ${z.art}`);
if (z.titel !== titel) abweichungen.push(`${b}: titel = ${z.titel}`);
if (z.text !== text) abweichungen.push(`${b}: text = ${z.text}`);
if (z.bewertung !== bew) abweichungen.push(`${b}: bewertung = ${z.bewertung}`);
if (z.dringlichkeit !== dring) abweichungen.push(`${b}: dringlichkeit = ${z.dringlichkeit}`);
if (z.creator_id !== idLuna) abweichungen.push(`${b}: creator_id = ${z.creator_id}`);
if (z.erstellt_von !== idDogi) abweichungen.push(`${b}: erstellt_von = ${z.erstellt_von}`);
});
ok(abweichungen.length === 0,
abweichungen.length ? `Inhalte haben sich veraendert: ${abweichungen.slice(0, 5).join(" | ")}`
: `jedes Feld jeder Zeile ist unveraendert (${alle.length} Zeilen x 8 Felder geprueft)`);
/* Auch die SELTENEN Spalten -- gerade die vergisst man beim
Abschreiben des Bauplans. */
const mitHook = alle.find((z) => z.bereich === "content");
const mitExtern = alle.find((z) => z.bereich === "live");
ok(/Das habt ihr noch nie gesehen/.test(mitHook?.hook || ""),
`die Spalte "hook" hat ihren Inhalt behalten ("${String(mitHook?.hook).slice(0, 30)}…")`);
ok(mitExtern?.creator_extern === "Gastauftritt Mia",
`die Spalte "creator_extern" ebenfalls ("${mitExtern?.creator_extern}")`);
/* Der Index muss nach dem Tausch wieder da sein -- er wird beim DROP
mit entfernt. Ohne ihn laeuft alles weiter, nur langsam, und
niemand merkt es je. */
const indizes = d.prepare(
"SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='eintraege'").all()
.map((r) => r.name);
ok(indizes.includes("idx_eintraege_bereich"),
`der Index steht wieder (${indizes.join(", ")})`);
d.close();
}
/* GEGENPROBE, DIE WICHTIGSTE: Die Schranke muss noch BEISSEN. Eine
Umstellung, die nebenbei den CHECK entfernt, sieht aus wie ein Erfolg
und ist der schlimmere Ausgang -- ab da kann jeder Tippfehler einen
Bereich erfinden, den es nicht gibt. */
{
const d = new DatabaseSync(DB);
let abgelehnt = false;
try {
d.prepare(`INSERT INTO eintraege (bereich, art, titel, datum, erstellt)
VALUES ('erfundenerbereich','x','Darf nicht',?,?)`).run(heute, jetzt);
} catch { abgelehnt = true; }
ok(abgelehnt, "ein erfundener Bereich wird weiterhin abgelehnt — der CHECK beisst noch");
let angenommen = false;
try {
d.prepare(`INSERT INTO eintraege (bereich, art, titel, datum, erstellt)
VALUES ('agentur','kampagne','Direkt in die Datenbank',?,?)`).run(heute, jetzt);
angenommen = true;
d.exec("DELETE FROM eintraege WHERE titel = 'Direkt in die Datenbank'");
} catch { /* bleibt false */ }
ok(angenommen, "'agentur' wird jetzt angenommen");
d.close();
}
/* =======================================================================
4. Der Bereich im Betrieb
======================================================================= */
melde("\n=== Der Bereich im Betrieb ===");
async function anmelden(rolle, code) {
const a = await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle, code }) });
const keks = (a.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; ");
if (!keks) throw new Error(`Anmeldung ${rolle} fehlgeschlagen (${a.status})`);
return keks;
}
const keksDogi = await anmelden("admin", "CODE-DOGI-0001");
const keksLuna = await anmelden("creator", "CODE-CREA-0001");
async function ruf(art, weg, keks, koerper) {
const a = await fetch(BASIS + weg, {
method: art,
headers: { "Content-Type": "application/json", cookie: keks, origin: BASIS },
body: koerper ? JSON.stringify(koerper) : undefined,
});
let daten = null;
try { daten = await a.json(); } catch { /* kein JSON */ }
return { status: a.status, daten };
}
/* Die vier Arten -- jede einzeln, denn eine, die der Server nicht kennt,
waere im Formular waehlbar und beim Speichern ein Fehler. */
const ARTEN = ["kampagne", "schulung", "anliegen", "zustaendig"];
{
const { daten } = await ruf("GET", "/workspace/api/bereich/agentur", keksDogi);
ok(daten?.einstellung?.name === "Agentur",
`der Bereich meldet sich als "${daten?.einstellung?.name}"`);
const arten = Object.keys(daten?.einstellung?.arten || {});
ok(arten.length === 4 && ARTEN.every((a) => arten.includes(a)),
`vier Arten: ${arten.join(", ")}`);
ok(daten?.einstellung?.dringlichkeit === true,
"Dringlichkeit ist an — ein Anliegen von gestern ist etwas anderes als eines von vor drei Wochen");
ok(daten?.einstellung?.bewertung === false,
"Bewertung ist aus — hier wird zusammengearbeitet, nicht benotet");
}
let angelegt = 0;
for (const art of ARTEN) {
const { status } = await ruf("POST", "/workspace/api/bereich/agentur", keksDogi, {
art, titel: `Test ${art}`, text: "Inhalt", datum: heute,
dringlichkeit: "mittel", creator_id: idLuna,
});
if (status === 200 || status === 201) angelegt++;
else ok(false, `Art "${art}" liess sich nicht anlegen (HTTP ${status})`);
}
ok(angelegt === ARTEN.length, `alle ${angelegt} Arten lassen sich anlegen`);
/* GEGENPROBE: eine erfundene Art muss abgelehnt werden. Sonst pruefte
die Zeile darueber nur, dass der Server irgendetwas annimmt. */
{
const { status } = await ruf("POST", "/workspace/api/bereich/agentur", keksDogi, {
art: "erfundeneart", titel: "Darf nicht", datum: heute,
});
ok(status >= 400, `eine erfundene Art wird abgelehnt (HTTP ${status})`);
}
/* Der Creator SIEHT seinen Agentur-Bereich -- er ist der Grund, warum
es ihn gibt. */
{
const { status, daten } = await ruf("GET", "/workspace/api/bereich/agentur", keksLuna);
ok(status === 200, `Luna darf den Bereich oeffnen (HTTP ${status})`);
ok((daten?.eintraege || []).length === ARTEN.length,
`sie sieht ihre ${(daten?.eintraege || []).length} Eintraege`);
}
/* =======================================================================
5. Kommt er ueberall an, wo Bereiche vorkommen?
======================================================================= */
melde("\n=== Ueberall, wo Bereiche vorkommen ===");
{
/* Der Wochenbericht. Hier stand die Liste der fuenf Bereiche noch
einmal von Hand abgeschrieben -- der neue waere schlicht nicht
vorgekommen, ohne Fehler und ohne Luecke. */
const { daten } = await ruf("GET", "/workspace/api/report", keksDogi);
const b = daten?.bereiche || {};
ok(Object.keys(b).length === 6,
`der Wochenbericht kennt ${Object.keys(b).length} Bereiche: ${Object.keys(b).join(", ")}`);
ok(b.agentur && b.agentur.neu >= ARTEN.length,
`und zaehlt ${b.agentur?.neu} neue Agentur-Eintraege`);
}
{
/* Die Suche. Ein Treffer ohne Ueberschrift sieht aus wie ein Fehler. */
const { daten } = await ruf("GET", "/workspace/api/suche?q=" + encodeURIComponent("Test kampagne"), keksDogi);
const text = JSON.stringify(daten);
ok(/Agentur/.test(text),
"die Suche beschriftet Agentur-Treffer mit dem richtigen Namen");
}
/* =======================================================================
6. Die Kachel — mit einem echten Browser
======================================================================= */
melde("\n=== Die Kachel ===");
{
const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await pw.chromium.launch();
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
const seite = await ctx.newPage();
await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" });
await seite.click('.rolle[data-rolle="admin"]');
await seite.fill("#code", "CODE-DOGI-0001");
await seite.click("button[type=submit]");
await seite.waitForURL("**/start.html", { timeout: 25000 });
await seite.waitForTimeout(1100);
const kacheln = await seite.$$eval(".kachel", (k) => k.map((x) => ({
name: x.querySelector(".kachel__name")?.textContent.trim(),
ziel: x.querySelector(".kachel__link")?.getAttribute("href"),
ton: x.dataset.ton,
pfade: x.querySelectorAll(".kachel__zeichen path").length,
})));
const a = kacheln.find((x) => x.name === "Agentur");
ok(!!a, `die Kachel "Agentur" steht da (${kacheln.length} Kacheln insgesamt)`);
ok(a?.ziel === "bereich.html?b=agentur", `sie fuehrt nach "${a?.ziel}"`);
ok(Number(a?.pfade) >= 4, `ihr Zeichen hat ${a?.pfade} Pfade — es ist wirklich gezeichnet`);
/* KEINE ZWEI KACHELN MIT DEMSELBEN FARBTON NEBENEINANDER. Das Auge
vergleicht nur, was benachbart ist -- zwei gleiche Toene direkt
nebeneinander lesen sich als Fehler. */
const toene = kacheln.map((x) => x.ton);
const nachbarnGleich = toene.filter((t, i) => i > 0 && t === toene[i - 1]);
ok(nachbarnGleich.length === 0,
nachbarnGleich.length ? `zwei Nachbarkacheln haben denselben Ton (${nachbarnGleich.join(", ")})`
: `keine zwei benachbarten Kacheln teilen einen Farbton (${toene.length} geprueft)`);
/* Die Seite selbst muss aufgehen und ihren Namen tragen. */
await seite.goto(BASIS + "/workspace/bereich.html?b=agentur", { waitUntil: "networkidle" });
await seite.waitForTimeout(900);
const titel = await seite.$eval(".titel", (e) => e.textContent.trim()).catch(() => "");
ok(/Agentur/i.test(titel), `die Bereichsseite heisst "${titel}"`);
/* .eintrag-karte -- der Name aus bereich.js. Beim ersten Anlauf stand
hier geraten ".eintrag, .karte"; das traf nichts und meldete "0
Eintraege", obwohl vier dastanden. Ein falscher Selektor meldet
einen Fehler, den es nicht gibt -- genauso schaedlich wie einer,
der etwas uebersieht. */
const eintraege = await seite.$$eval(".eintrag-karte", (e) => e.length).catch(() => 0);
ok(eintraege >= ARTEN.length,
`sie zeigt ${eintraege} Eintraege (mindestens die ${ARTEN.length} angelegten)`);
/* Und die Titel muessen die sein, die oben angelegt wurden -- sonst
bewiese die Zahl nur, dass IRGENDWAS gezeichnet wurde. */
const kartenTitel = await seite.$$eval(".eintrag-titel", (e) => e.map((x) => x.textContent.trim()));
const fehlend = ARTEN.map((a) => `Test ${a}`).filter((t) => !kartenTitel.includes(t));
ok(fehlend.length === 0,
fehlend.length ? `diese Eintraege fehlen auf der Seite: ${fehlend.join(", ")}`
: `alle vier Titel stehen da: ${kartenTitel.join(" · ")}`);
/* GEGENPROBE zum Selektor: Ein erfundener Bereich darf NICHT wie ein
echter aussehen -- weder in der Ueberschrift noch mit Eintraegen.
Die Anwendung schickt einen dorthin zurueck, wo er hingehoert. */
await seite.goto(BASIS + "/workspace/bereich.html?b=gibtesnicht", { waitUntil: "networkidle" });
await seite.waitForTimeout(900);
const falsch = await seite.$eval(".titel", (e) => e.textContent.trim()).catch(() => "");
const falschKarten = await seite.$$eval(".eintrag-karte", (e) => e.length).catch(() => 0);
ok(!/Agentur/i.test(falsch) && falschKarten === 0,
`ein erfundener Bereich zeigt weder die Agentur noch Eintraege `
+ `("${falsch}", ${falschKarten} Karten)`);
await ctx.close();
await browser.close();
}
await serverStoppen();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
melde(`\n${gemacht} Pruefungen`);
melde(fehler === 0 ? "ALLES IN ORDNUNG" : `${fehler} FEHLER`);
process.exit(fehler ? 1 : 0);
+7
View File
@@ -19,7 +19,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-ampel");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4206";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 250_000).unref?.();
+7
View File
@@ -43,7 +43,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-aufgabenbrett");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4216";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.();
+7
View File
@@ -34,6 +34,7 @@ const ordner = mkdtempSync(join(tmpdir(), "ws-a11y-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
/* Der Port muss frei sein -- sonst wird gegen einen fremden Server
gemessen. Begruendung in helfer-port.mjs. */
import { notbremse } from "./helfer-notbremse.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4253, "die Barrierefreiheitspruefung");
@@ -45,6 +46,12 @@ const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-barrierefrei-workspace");
await new Promise((r) => setTimeout(r, 900));
const BASIS = "http://127.0.0.1:4253";
+45 -6
View File
@@ -37,6 +37,7 @@ const ordner = mkdtempSync(join(tmpdir(), "ws-breit-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
/* Der Port muss frei sein -- sonst wird gegen einen fremden Server
gemessen. Begruendung in helfer-port.mjs. */
import { notbremse } from "./helfer-notbremse.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4277, "die Breitenpruefung");
@@ -48,6 +49,12 @@ const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-breiten");
await new Promise((r) => setTimeout(r, 900));
const BASIS = "http://127.0.0.1:4277";
@@ -130,12 +137,44 @@ const BREITEN = [
[2560, 1440, "sehr breit"],
];
const SEITEN = [
"start.html", "uebersicht.html", "aufgaben.html", "kalender.html", "calls.html",
"dateien.html", "content.html", "bereich.html?b=live", "profil.html",
"startcheck.html", "report.html", "scouting.html", "personen.html",
"wissen.html", "automation.html", "steckbrief.html",
];
/* ---------- WELCHE SEITEN? ALLE. -----------------------------------------
Hier stand eine Liste von Hand -- und wie ihre Schwester in
pruef-handy.mjs war sie veraltet: chat.html und leistung.html fehlten.
Ausgerechnet der Chat, den man auf dem Handy am ehesten benutzt, war
bei keiner einzigen der neun Breiten je gemessen worden.
Eine Liste von Hand altert in dem Moment, in dem jemand weiterarbeitet,
und sie sagt nichts dazu. Der Lauf blieb gruen, die Zahl der
Pruefungen stimmte -- gemessen wurde trotzdem zu wenig. Deshalb kommt
sie jetzt aus dem Verzeichnis: Die naechste neue Seite ist von selbst
dabei, so wie sie durch die Schranke in workspace.js von selbst
geschuetzt ist.
index.html bleibt draussen -- die Anmeldeseite leitet nach der
Anmeldung weiter und wuerde hier nur start.html ein zweites Mal
messen. */
const { readdirSync } = await import("node:fs");
const { dirname } = await import("node:path");
const { fileURLToPath } = await import("node:url");
/* Der Ort DIESER Datei, nicht process.cwd(): Der Gesamtlauf startet vom
Wurzelverzeichnis, von Hand startet man aus server/. */
const SEITEN_ORDNER = join(dirname(fileURLToPath(import.meta.url)), "..", "workspace");
const MIT_ABFRAGE = { "bereich.html": "?b=live" };
const SEITEN = readdirSync(SEITEN_ORDNER)
.filter((n) => n.endsWith(".html") && n !== "index.html")
.sort()
.map((n) => n + (MIT_ABFRAGE[n] || ""));
/* Die Zahl gehoert in die BEDINGUNG, nicht nur in den Meldetext: Faende
readdirSync nichts, liefe die Schleife unten null Mal -- und der Lauf
waere gruen, ohne eine einzige Seite angesehen zu haben. */
if (SEITEN.length < 15) {
console.log(` FEHL nur ${SEITEN.length} Seiten in ${SEITEN_ORDNER} gefunden `
+ "-- die Breitenpruefung kann so nichts messen");
process.exit(1);
}
console.log(`${SEITEN.length} Seiten x ${BREITEN.length} Breiten: ${SEITEN.join(", ")}`);
const messen = () => {
const sichtbar = (e) => {
+7
View File
@@ -39,6 +39,7 @@ const ordner = mkdtempSync(join(tmpdir(), "ws-browser-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
/* Der Port muss frei sein -- sonst wird gegen einen fremden Server
gemessen. Begruendung in helfer-port.mjs. */
import { notbremse } from "./helfer-notbremse.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4289, "die Browserpruefung");
@@ -50,6 +51,12 @@ const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-browser");
await new Promise((r) => setTimeout(r, 900));
const BASIS = "http://127.0.0.1:4289";
+7
View File
@@ -25,7 +25,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-buehne");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4193";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 220_000).unref?.();
+126 -35
View File
@@ -26,6 +26,7 @@ import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-callkat-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
import { notbremse } from "./helfer-notbremse.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4321, "die Kategorienpruefung");
@@ -37,6 +38,12 @@ const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-call-kategorien");
await new Promise((r) => setTimeout(r, 1000));
const BASIS = "http://127.0.0.1:4321";
@@ -81,25 +88,89 @@ const tag = (versatz) => {
const p = (n) => String(n).padStart(2, "0");
return `${t.getFullYear()}-${p(t.getMonth() + 1)}-${p(t.getDate())}`;
};
function call(titel, versatz) {
/* ---------------------------------------------------------------------
DIE WANDUHR DARF KEINE ANNAHME SEIN (korrigiert 06.09.2026)
Hier stand vorher `${tag(versatz)}T18:00` und die feste Erwartung
"zwei Termine unter Heute". Das ging gut, solange jemand vormittags
prueft. Ab 18 Uhr sind die beiden heutigen Calls VORBEI -- der Server
schiebt sie dann von "anstehend" nach "Protokoll fehlt", und die
Oberflaeche unterteilt erst ab FUENF anstehenden ueberhaupt in
Zeitraeume. Uebrig blieben vier, die Bloecke verschwanden, und die
Pruefung meldete "0 Kategorien".
Sie blieb daraufhin an einem Klick auf einen Block haengen, der gar
nicht da war -- DREI STUNDEN, und mit ihr der ganze Regressionslauf.
Der Fehler war also nicht nur falsch, er war auch teuer.
Deshalb jetzt zweierlei:
1. Die Zeiten liegen RELATIV zu jetzt, nicht auf einer festen
Uhrzeit. Was "heute" heisst, wird nicht behauptet, sondern
gerechnet.
2. Die Erwartung wird mit DERSELBEN Vorschrift aus den gesaeten
Zeitpunkten abgeleitet, die auch calls.js benutzt -- statt
Zahlen hinzuschreiben, die nur zu einer bestimmten Tageszeit
stimmen. Um 23:58 faellt "in 35 Minuten" auf morgen, und dann
erwartet die Pruefung ihn auch dort.
Eine Pruefung, die um 17:59 gruen und um 18:01 rot ist, ohne dass
sich am Code etwas geaendert hat, misst den Kalender, nicht die
Software. */
function callZeit(dat, titel) {
const p = (n) => String(n).padStart(2, "0");
const beginn = `${dat.getFullYear()}-${p(dat.getMonth() + 1)}-${p(dat.getDate())}`
+ `T${p(dat.getHours())}:${p(dat.getMinutes())}`;
d.prepare(`INSERT INTO termine (titel, art, beginn, dauer_min, creator_id,
teilnehmer_id, erstellt, erstellt_von) VALUES (?,'call',?,30,?,?,?,?)`)
.run(titel, `${tag(versatz)}T18:00`, idLuna, idLuna, jetzt, idDogi);
.run(titel, beginn, idLuna, idLuna, jetzt, idDogi);
return beginn;
}
/* WIE VIELE TAGE bis Sonntag? Dieselbe Rechnung wie in calls.js --
damit die Prüfung weiss, was sie erwarten darf, statt es zu raten. */
const heute = new Date();
const wochentag = (heute.getDay() + 6) % 7; // Mo=0 … So=6
const bisSonntag = 6 - wochentag;
/** WOERTLICH dieselbe Vorschrift wie zeitraumVon() in calls.js.
* Absichtlich abgeschrieben und nicht importiert: calls.js laeuft im
* Browser und kennt kein export. Waere sie hier anders, pruefte die
* Datei ihre eigene Rechnung statt die der Anwendung -- deshalb steht
* weiter unten eine Gegenprobe, die beide gegeneinander haelt. */
function zeitraumVon(beginn) {
const dd = new Date(String(beginn).replace(" ", "T"));
const h = new Date(); h.setHours(0, 0, 0, 0);
const t = new Date(dd); t.setHours(0, 0, 0, 0);
const tage = Math.round((t - h) / 86400000);
if (tage <= 0) return "heute";
const wt = (h.getDay() + 6) % 7; // Mo=0 … So=6
const bisSo = 6 - wt;
if (tage <= bisSo) return "woche";
if (tage <= bisSo + 7) return "naechste";
return "spaeter";
}
/* Zwei heute. Dann einer, der sicher noch in diese Woche fällt (falls
es sie gibt), einer in die nächste, und drei weit weg. */
call("Heute A", 0);
call("Heute B", 0);
if (bisSonntag >= 1) call("Diese Woche", 1);
call("Nächste Woche", bisSonntag + 2);
for (let i = 0; i < 3; i++) call(`Später ${i}`, bisSonntag + 20 + i);
const nachher = (min) => new Date(Date.now() + min * 60000);
/* SIEBEN kuenftige Calls, gestreut. Sieben, weil die Oberflaeche erst
ab fuenf anstehenden unterteilt -- mit vieren pruefte diese Datei
einen Zustand, in dem es die Bloecke gar nicht geben soll. */
/* Die Titel getrennt, weil weiter unten drei davon wieder geloescht
werden muessen -- ueber genau diese Liste und nicht ueber ein
abgeschriebenes Namensmuster. */
const GESAET_TITEL = ["Gleich", "Etwas spaeter", "Morgen", "In drei Tagen",
"In neun Tagen", "In dreissig Tagen", "In einunddreissig Tagen"];
const GESAET = [
callZeit(nachher(5), "Gleich"),
callZeit(nachher(35), "Etwas spaeter"),
callZeit(nachher(60 * 26), "Morgen"),
callZeit(nachher(60 * 24 * 3), "In drei Tagen"),
callZeit(nachher(60 * 24 * 9), "In neun Tagen"),
callZeit(nachher(60 * 24 * 30), "In dreissig Tagen"),
callZeit(nachher(60 * 24 * 31), "In einunddreissig Tagen"),
];
/* Was MUSS also in welchem Block stehen? Gerechnet, nicht behauptet. */
const ERWARTET = {};
for (const b of GESAET) {
const z = zeitraumVon(b);
ERWARTET[z] = (ERWARTET[z] || 0) + 1;
}
d.close();
const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
@@ -133,26 +204,32 @@ const { ctx, seite } = await callsSeite();
/* DIE ANZAHL IN DIE BEDINGUNG: Ohne Blöcke liefe alles Folgende über
eine leere Menge und meldete grün. */
ok(bloecke.length >= 3, `${bloecke.length} Kategorien (erwartet mindestens 3)`);
const erwarteteRaeume = Object.keys(ERWARTET);
ok(bloecke.length === erwarteteRaeume.length,
`${bloecke.length} Kategorien (gerechnet: ${erwarteteRaeume.length} — `
+ `${erwarteteRaeume.map((r) => `${r}:${ERWARTET[r]}`).join(", ")})`);
const heuteBlock = bloecke.find((b) => b.raum === "heute");
ok(heuteBlock?.zahl === 2, `"Heute" enthält ${heuteBlock?.zahl} Termine (erwartet 2)`);
/* JEDER gerechnete Zeitraum einzeln. Nicht "mindestens drei": Welche
Bloecke es gibt, haengt vom Wochentag ab (am Sonntag gibt es kein
"Diese Woche" mehr, und das ist richtig). Gerechnet wird es mit
derselben Vorschrift wie in der Anwendung. */
let stimmen = 0;
for (const [raum, soll] of Object.entries(ERWARTET)) {
const b = bloecke.find((x) => x.raum === raum);
const gut = b?.zahl === soll;
if (gut) stimmen++;
ok(gut, `"${raum}" enthält ${b?.zahl ?? "keinen Block"} (gerechnet: ${soll})`);
}
ok(stimmen === erwarteteRaeume.length && stimmen >= 3,
`${stimmen} von ${erwarteteRaeume.length} Zeitraeumen stimmen (mindestens 3 mussten es sein)`);
const spaeter = bloecke.find((b) => b.raum === "spaeter");
ok(spaeter?.zahl === 3, `"Später" enthält ${spaeter?.zahl} (erwartet 3)`);
const naechste = bloecke.find((b) => b.raum === "naechste");
ok(naechste?.zahl === 1, `"Nächste Woche" enthält ${naechste?.zahl} (erwartet 1)`);
/* Am Sonntag gibt es kein "Diese Woche" mehr -- das ist richtig und
kein Fehler. Deshalb wird die Erwartung aus dem Wochentag
abgeleitet, nicht festgeschrieben. */
const diese = bloecke.find((b) => b.raum === "woche");
const erwarteDiese = bisSonntag >= 1;
ok(!!diese === erwarteDiese,
erwarteDiese
? `"Diese Woche" ist da (${diese?.zahl}) — heute ist noch ${bisSonntag} Tag(e) bis Sonntag`
: `"Diese Woche" fehlt zu Recht — heute ist Sonntag, danach kommt nichts mehr`);
/* GEGENPROBE zur abgeschriebenen Vorschrift: Die Summe ueber alle
Bloecke muss die Zahl der gesaeten Calls sein. Rechnete diese Datei
anders als die Anwendung, ginge die Summe nicht auf -- und genau
das faellt sonst nicht auf, weil beide Seiten "plausibel" aussehen. */
const summe = bloecke.reduce((n, b) => n + b.zahl, 0);
ok(summe === GESAET.length,
`alle ${GESAET.length} Calls sind in genau einem Block gelandet (gezaehlt: ${summe})`);
/* Die Zahl im Kopf muss zu den Karten darin passen. Eine Zahl, die
etwas anderes sagt als der Inhalt, ist schlimmer als keine. */
@@ -211,10 +288,17 @@ melde("\n=== Wenige Termine brauchen keine Schubladen ===");
/* Vier Karten in vier Kategorien wären mehr Arbeit als Nutzen -- man
klickt dreimal, um vier Zeilen zu sehen. */
const p = new DatabaseSync(process.env.WORKSPACE_DB);
p.prepare("DELETE FROM termine WHERE titel LIKE 'Später%' OR titel = 'Nächste Woche'").run();
/* Drei der sieben weg, vier bleiben -- unterhalb der Schwelle, ab der
die Oberflaeche ueberhaupt unterteilt. Geloescht wird ueber die
TITEL AUS DER SAAT oben, nicht ueber Namen von Hand: Als die Saat
am 06.09.2026 umgestellt wurde, traf das alte `LIKE 'Später%'`
nichts mehr, es blieben sieben Termine stehen, und dieser Abschnitt
pruefte ab da einen Zustand, den er selbst nie hergestellt hatte. */
const WEG = GESAET_TITEL.slice(-3);
p.prepare(`DELETE FROM termine WHERE titel IN (${WEG.map(() => "?").join(",")})`).run(...WEG);
const rest = p.prepare("SELECT COUNT(*) AS n FROM termine").get().n;
p.close();
ok(rest <= 4, `nur noch ${rest} Termine übrig`);
ok(rest === 4, `nur noch ${rest} Termine übrig (drei entfernt: ${WEG.join(", ")})`);
await seite.reload({ waitUntil: "networkidle" });
await seite.waitForTimeout(1400);
@@ -234,10 +318,17 @@ melde("\n=== Gegenprobe ===");
gerade bewiesen, dass sie VERSCHWINDEN -- ohne diesen Nachweis
bewiese "0 Blöcke" nur, dass der Selektor nie etwas trifft. */
const p = new DatabaseSync(process.env.WORKSPACE_DB);
const pp = (n) => String(n).padStart(2, "0");
for (let i = 0; i < 6; i++) {
/* Relativ zu jetzt, wie oben. Eine feste Uhrzeit haette hier
denselben Zeitzuender wie in der Saat: Ab 18 Uhr waeren die neuen
Termine schon vorbei und die Gegenprobe bewiese nichts. */
const t = new Date(Date.now() + (40 + i) * 86400000);
const beginn = `${t.getFullYear()}-${pp(t.getMonth() + 1)}-${pp(t.getDate())}`
+ `T${pp(t.getHours())}:${pp(t.getMinutes())}`;
p.prepare(`INSERT INTO termine (titel, art, beginn, dauer_min, creator_id,
teilnehmer_id, erstellt, erstellt_von) VALUES (?,'call',?,30,?,?,?,?)`)
.run(`Wieder da ${i}`, `${tag(bisSonntag + 30 + i)}T18:00`, idLuna, idLuna, jetzt, idDogi);
.run(`Wieder da ${i}`, beginn, idLuna, idLuna, jetzt, idDogi);
}
p.close();
+7
View File
@@ -30,6 +30,7 @@ import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-chatoptik-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
import { notbremse } from "./helfer-notbremse.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4317, "die Chat-Optikpruefung");
@@ -41,6 +42,12 @@ const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-chat-optik");
await new Promise((r) => setTimeout(r, 1000));
const BASIS = "http://127.0.0.1:4317";
+7
View File
@@ -27,7 +27,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-checkliste");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4207";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.();
+7
View File
@@ -34,7 +34,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-code");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4195";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 240_000).unref?.();
+7
View File
@@ -15,7 +15,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-content-ansicht");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4185";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.();
+7
View File
@@ -33,6 +33,7 @@ import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-dabei-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
import { notbremse } from "./helfer-notbremse.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4293, "die Optikpruefung der Teilnehmerwahl");
@@ -44,6 +45,12 @@ const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-dabei-optik");
await new Promise((r) => setTimeout(r, 900));
const BASIS = "http://127.0.0.1:4293";
+7
View File
@@ -29,7 +29,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-formulare");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4203";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 250_000).unref?.();
+7
View File
@@ -35,7 +35,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-freie-namen");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4192";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 240_000).unref?.();
+7
View File
@@ -51,6 +51,7 @@ import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-glocke-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
import { notbremse } from "./helfer-notbremse.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4299, "die Glockenpruefung");
@@ -62,6 +63,12 @@ const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-glocke");
await new Promise((r) => setTimeout(r, 900));
const BASIS = "http://127.0.0.1:4299";
+7
View File
@@ -53,7 +53,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-grosscheck");
await new Promise((r) => setTimeout(r, 800));
const BASIS = "http://127.0.0.1:4218";
setTimeout(() => { console.log("ABBRUCH — zu lange gelaufen"); process.exit(1); }, 900_000).unref?.();
+7
View File
@@ -36,7 +36,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-handy");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4198";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 400_000).unref?.();
+7
View File
@@ -22,7 +22,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-kalender");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4188";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.();
+7
View File
@@ -35,7 +35,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-kopf-messen");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4179";
+7
View File
@@ -35,6 +35,7 @@ import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-leistoptik-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
import { notbremse } from "./helfer-notbremse.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4337, "die Zahlen-Optikpruefung");
@@ -46,6 +47,12 @@ const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-leistung-optik");
await new Promise((r) => setTimeout(r, 1000));
const BASIS = "http://127.0.0.1:4337";
+7
View File
@@ -36,7 +36,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-lesbarkeit");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4214";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.();
+7
View File
@@ -47,7 +47,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-manager-sicht");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4199";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.();
+7
View File
@@ -33,7 +33,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-personen-formular");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4198";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.();
+7
View File
@@ -27,7 +27,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-personen-liste");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4187";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.();
+7
View File
@@ -19,7 +19,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-protokoll-klappe");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4180";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.();
+7
View File
@@ -40,7 +40,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-protokoll-loeschen");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4197";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.();
+7
View File
@@ -42,7 +42,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-rollen");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4193";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 540_000).unref?.();
+7
View File
@@ -26,7 +26,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-schulung");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4205";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 250_000).unref?.();
+7
View File
@@ -34,7 +34,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-scout-zuteilung");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4217";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.();
+7
View File
@@ -38,7 +38,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-serien");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4189";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.();
+7
View File
@@ -34,7 +34,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-sicht");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4215";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.();
+7
View File
@@ -40,7 +40,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-sprung");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4212";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.();
+7
View File
@@ -15,7 +15,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-start-ansicht");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4186";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.();
+7
View File
@@ -19,7 +19,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-steckbrief");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4196";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.();
+7
View File
@@ -28,6 +28,7 @@ import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-tagesblick-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
import { notbremse } from "./helfer-notbremse.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4303, "die Tagesblickpruefung");
@@ -39,6 +40,12 @@ const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-tagesblick");
await new Promise((r) => setTimeout(r, 900));
const BASIS = "http://127.0.0.1:4303";
+7
View File
@@ -38,7 +38,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-team");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4213";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.();
+7
View File
@@ -37,6 +37,7 @@ const ordner = mkdtempSync(join(tmpdir(), "ws-tempo-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
/* Der Port muss frei sein -- sonst wird gegen einen fremden Server
gemessen. Begruendung in helfer-port.mjs. */
import { notbremse } from "./helfer-notbremse.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4283, "die Tempopruefung");
@@ -48,6 +49,12 @@ const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-tempo-workspace");
await new Promise((r) => setTimeout(r, 900));
const BASIS = "http://127.0.0.1:4283";
+11 -5
View File
@@ -23,7 +23,14 @@ const echteCookie = express.response.cookie;
express.response.cookie = function (name, wert, opt) {
return echteCookie.call(this, name, wert, { ...(opt || {}), secure: false });
};
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(150_000, "pruef-uebersicht-browser");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4177";
@@ -38,11 +45,10 @@ const tagText = (d = new Date()) =>
let fehler = 0;
const ok = (b, t) => { console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const notbremse = setTimeout(() => {
console.log("\nABBRUCH: hing über 150 Sekunden.");
process.exit(1);
}, 150_000);
notbremse.unref?.();
/* Die eigene Notbremse von hier ist am 06.09.2026 nach
helfer-notbremse.mjs gewandert und wird oben aufgerufen -- sie faengt
jetzt zusaetzlich den unbehandelten Absturz ab, nicht nur das
Zulangsamsein. Die knappe Frist dieser Datei bleibt erhalten. */
/* ---------- Daten ------------------------------------------------------ */
+7
View File
@@ -23,7 +23,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-wissen-neu");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4201";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.();
+7
View File
@@ -18,7 +18,14 @@ const echteCookie = express.response.cookie;
express.response.cookie = function (n, w, o) {
return echteCookie.call(this, n, w, { ...(o || {}), secure: false });
};
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-workspace-seiten");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4178";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 150_000).unref?.();
+7
View File
@@ -17,7 +17,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-zustand-ansicht");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4182";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 180_000).unref?.();
+37
View File
@@ -75,6 +75,43 @@ export const BEREICHE = {
bewertung: false,
dringlichkeit: true,
},
/* DER SECHSTE BEREICH (06.09.2026).
Kernprinzip 04 des Konzepts: "Die Agentur bleibt angebunden." Fuenf
Bereiche standen, dieser fehlte -- und damit fehlte der einzige
Ort, an dem steht, welche Kampagne laeuft, welche Schulung ansteht
und wie ein Anliegen an die Agentur ausgegangen ist. Das lief
ueber private Nachrichten: nicht auffindbar, nicht nachvollziehbar,
und beim naechsten Mal fing man wieder von vorn an.
VIER ARTEN, und die vierte ist der Punkt:
kampagne was laeuft, bis wann, unter welcher Bedingung
schulung Termine und Inhalte, verweist auf die Bibliothek
anliegen der OFFIZIELLE Weg hin -- mit Stand
zustaendig wer wofuer zustaendig ist: wir oder die Agentur
Die letzte Art ist keine Verlegenheit, sondern das, was das Konzept
ausdruecklich fordert ("Betreuung & Umsetzung ist unsere Seite,
offizielle Wege und Plattformthemen sind Agenturseite"). Solange
das nur im Kopf steht, wird bei jedem Streitfall neu verhandelt,
wer eigentlich haette handeln muessen.
Dringlichkeit: ja. Ein Anliegen an die Agentur, das drei Wochen
liegt, ist etwas anderes als eines von gestern -- und ohne Stufe
sieht man den Unterschied in einer Liste nicht.
Bewertung: nein. Hier wird nichts benotet; es ist ein
Zusammenarbeits-Bereich, kein Analysebereich. */
agentur: {
name: "Agentur",
arten: {
kampagne: "Event & Kampagne",
schulung: "Schulung",
anliegen: "Anliegen an die Agentur",
zustaendig: "Zuständigkeit",
},
bewertung: false,
dringlichkeit: true,
},
};
const DRINGLICHKEITEN = ["hoch", "mittel", "niedrig"];
+7 -1
View File
@@ -22,6 +22,7 @@ import express from "express";
import {
db, protokolliere, echteIp, sitzungLesen, betreuteIds, darfCreator, istLeitung, istDogFather, sichtbareCreatorIds,
} from "./workspace.js";
import { BEREICHE } from "./workspace-bereiche.js";
export const reportRouter = express.Router();
@@ -135,7 +136,12 @@ reportRouter.get("/workspace/api/report", (req, res) => {
/* --- Bereiche -------------------------------------------------------- */
const bereiche = {};
for (const b of ["live", "content", "technik", "community", "schutz"]) {
/* AUS DER EINEN QUELLE, nicht abgeschrieben (06.09.2026).
Hier stand die Liste der fuenf Bereiche noch einmal von Hand. Als
"agentur" dazukam, waere er im Wochenbericht schlicht nicht
vorgekommen -- ohne Fehler, ohne Luecke, einfach nicht da. Genau
so altert eine abgeschriebene Liste. */
for (const b of Object.keys(BEREICHE)) {
bereiche[b] = {
neu: zahl(
`SELECT COUNT(*) AS n FROM eintraege e WHERE e.bereich = ? AND e.erstellt >= ?${nurE}`,
+7 -5
View File
@@ -30,7 +30,7 @@ import {
import { sichtbar as sichtbarAufgaben } from "./workspace-aufgaben.js";
import { sichtbar as sichtbarTermine } from "./workspace-kalender.js";
import { sichtbar as sichtbarDateien } from "./workspace-dateien.js";
import { sichtbar as sichtbarEintraege } from "./workspace-bereiche.js";
import { sichtbar as sichtbarEintraege, BEREICHE } from "./workspace-bereiche.js";
export const sucheRouter = express.Router();
@@ -38,10 +38,12 @@ const MIN = 2;
const JE_BEREICH = 6;
const AUSSCHNITT = 110;
const BEREICHSNAME = {
live: "LIVE-Analyse", content: "Content", technik: "Technik",
community: "Community", schutz: "Schutz",
};
/* Die Namen kommen aus workspace-bereiche.js, nicht aus einer zweiten
Liste hier. Eine abgeschriebene Liste vergisst den naechsten neuen
Bereich -- und in der Suche hiesse das: Er wird gefunden und traegt
dann keine Ueberschrift. */
const BEREICHSNAME = Object.fromEntries(
Object.entries(BEREICHE).map(([k, b]) => [k, b.name]));
function angemeldet(req, res, next) {
const person = sitzungLesen(req);
+95 -1
View File
@@ -444,6 +444,100 @@ function umstellungen(d) {
}
}
/* ---- Der sechste Bereich: "agentur" (06.09.2026) ----
Kernprinzip 04 des Konzepts lautet woertlich "Die Agentur bleibt
angebunden". Fuenf der sechs Bereiche standen (LIVE, Content,
Technik, Community, Schutz) -- der sechste fehlte vollstaendig. Was
dadurch nirgends stand: welche Kampagne laeuft und bis wann, welche
Schulung ansteht, und vor allem der OFFIZIELLE WEG zur Agentur mit
Stand und Antwort. Das lief bisher ueber private Nachrichten und
war damit weder auffindbar noch nachvollziehbar.
Und die Trennung, die das Konzept ausdruecklich verlangt und die im
Alltag sonst verschwimmt: Betreuung und Umsetzung sind UNSERE
Seite, offizielle Wege und Plattformthemen die der Agentur. Wer
wofuer zustaendig ist, gehoert auf den Bildschirm, nicht ins
Gedaechtnis.
Derselbe Umbau wie bei "abgebrochen" darueber -- ein CHECK laesst
sich in SQLite nicht aendern, die Tabelle muss neu gebaut werden:
erst sichern, dann tauschen, Zeilen zaehlen, Verweise pruefen. */
const eintraegePlan = d.prepare(
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'eintraege'").get()?.sql || "";
if (eintraegePlan && !eintraegePlan.includes("'agentur'")) {
const sicherung = `${DB_PFAD}.vor-agentur-${jetztStempel}`;
try {
d.exec(`VACUUM INTO '${sicherung.replace(/'/g, "''")}'`);
console.log("[workspace] Sicherung vor der Umstellung:", sicherung);
} catch (fehler) {
console.error("[workspace] Sicherung fehlgeschlagen, Umstellung abgebrochen:", fehler?.message);
return;
}
d.exec("PRAGMA foreign_keys = OFF");
try {
const vorher = d.prepare("SELECT COUNT(*) AS n FROM eintraege").get().n;
d.exec("BEGIN");
d.exec(`
CREATE TABLE eintraege_neu (
id INTEGER PRIMARY KEY AUTOINCREMENT,
bereich TEXT NOT NULL
CHECK (bereich IN ('live','content','technik','community','schutz','agentur')),
art TEXT NOT NULL,
titel TEXT NOT NULL,
text TEXT,
datum TEXT NOT NULL,
bewertung INTEGER CHECK (bewertung IS NULL OR (bewertung BETWEEN 1 AND 5)),
dringlichkeit TEXT NOT NULL DEFAULT 'mittel'
CHECK (dringlichkeit IN ('hoch','mittel','niedrig')),
status TEXT NOT NULL DEFAULT 'offen'
CHECK (status IN ('offen','erledigt')),
creator_id INTEGER REFERENCES personen(id) ON DELETE SET NULL,
erstellt TEXT NOT NULL,
erstellt_von INTEGER REFERENCES personen(id) ON DELETE SET NULL,
geaendert TEXT,
hook TEXT,
format TEXT,
saeule_id INTEGER,
geplant TEXT,
creator_extern TEXT
);
INSERT INTO eintraege_neu
(id, bereich, art, titel, text, datum, bewertung, dringlichkeit, status,
creator_id, erstellt, erstellt_von, geaendert,
hook, format, saeule_id, geplant, creator_extern)
SELECT id, bereich, art, titel, text, datum, bewertung, dringlichkeit, status,
creator_id, erstellt, erstellt_von, geaendert,
hook, format, saeule_id, geplant, creator_extern
FROM eintraege;
DROP TABLE eintraege;
ALTER TABLE eintraege_neu RENAME TO eintraege;
CREATE INDEX IF NOT EXISTS idx_eintraege_bereich ON eintraege (bereich, creator_id);
`);
const nachher = d.prepare("SELECT COUNT(*) AS n FROM eintraege").get().n;
if (nachher !== vorher) {
d.exec("ROLLBACK");
console.error(`[workspace] Umstellung abgebrochen: ${vorher} Eintraege vorher, `
+ `${nachher} nachher. Sicherung: ${sicherung}`);
} else {
d.exec("COMMIT");
const kaputt = d.prepare("PRAGMA foreign_key_check").all();
if (kaputt.length) {
console.error("[workspace] ACHTUNG: nach der Umstellung", kaputt.length,
"verwaiste Verweise. Sicherung liegt unter", sicherung);
} else {
console.log(`[workspace] Bereich 'agentur' freigeschaltet, ${vorher} Eintraege, Verweise geprueft.`);
}
}
} catch (fehler) {
try { d.exec("ROLLBACK"); } catch { /* schon zurueckgerollt */ }
console.error("[workspace] Umstellung 'agentur' fehlgeschlagen:", fehler?.message);
} finally {
d.exec("PRAGMA foreign_keys = ON");
}
}
/* ---- Rolle "manager" erlauben ---- */
const bauplan = d.prepare(
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'personen'").get()?.sql || "";
@@ -918,7 +1012,7 @@ export function db() {
CREATE TABLE IF NOT EXISTS eintraege (
id INTEGER PRIMARY KEY AUTOINCREMENT,
bereich TEXT NOT NULL
CHECK (bereich IN ('live','content','technik','community','schutz')),
CHECK (bereich IN ('live','content','technik','community','schutz','agentur')),
art TEXT NOT NULL,
titel TEXT NOT NULL,
text TEXT,
+55 -1
View File
@@ -26,19 +26,73 @@ const dateien = readdirSync(ORDNER)
console.log(`${dateien.length} Pruefdateien gefunden\n`);
/* ---------- JEDE PRUEFUNG BEKOMMT EINE FRIST (06.09.2026) ---------------
VIERTER GRUND fuer diesen eigenen Laeufer, und er hat mich an einem
Tag ZWEIMAL erwischt:
Eine Pruefung, die haengt, bringt den GANZEN Lauf zum Stillstand --
ohne Fehlermeldung, ohne FEHL, ohne Absturz. Es steht einfach nichts
mehr da. Von aussen sieht "noch nicht fertig" genauso aus wie "haengt
fuer immer", und deshalb faellt es erst Stunden spaeter auf.
* pruef-alle-wege lief in den Ereignisstrom des Chats und wartete
auf ein Ende, das es nie gibt (behoben in der Datei selbst).
* pruef-call-kategorien blieb DREI STUNDEN stehen. Sie hatte, anders
als pruef-handy, keine eigene Notbremse.
Einzelne Dateien nachzuruesten hilft genau so lange, bis jemand die
naechste ohne Notbremse schreibt. Deshalb sitzt sie jetzt HIER, wo
sie fuer alle gilt -- auch fuer die, die es noch nicht gibt.
Und eine ueberschrittene Frist ist ein FEHLER, kein Ueberspringen: Sie
wird gezaehlt, gemeldet und mit der letzten Ausgabe der Datei belegt,
damit man sieht, wo sie stehengeblieben ist. Ein stiller Stillstand
waere das Gegenteil.
600 Sekunden sind grosszuegig bemessen: Die langsamste ehrliche
Pruefung im Haus (pruef-barrierefrei-workspace) braucht 190 s. Wer
das Dreifache davon ueberschreitet, wartet nicht -- er haengt. */
const FRIST_MS = 600_000;
const laufen = (datei) => new Promise((fertig) => {
const start = Date.now();
const kind = spawn(process.execPath, [join(ORDNER, datei)], {
stdio: ["ignore", "pipe", "pipe"],
});
let aus = "";
let abgewuergt = false;
kind.stdout.on("data", (d) => { aus += d; });
kind.stderr.on("data", (d) => { aus += d; });
const wecker = setTimeout(() => {
abgewuergt = true;
/* Erst freundlich, dann bestimmt. Ein Playwright-Browser haengt
sonst als Waise weiter und belegt Speicher bis zum Neustart. */
try { kind.kill("SIGTERM"); } catch { /* schon weg */ }
setTimeout(() => { try { kind.kill("SIGKILL"); } catch { /* schon weg */ } }, 3000);
}, FRIST_MS);
kind.on("close", (code) => {
clearTimeout(wecker);
const sek = Math.round((Date.now() - start) / 100) / 10;
const einzeln = (aus.match(/^\s{2}(ok|FEHL)\s/gm) || []).length;
const schlecht = (aus.match(/^\s{2}FEHL\s/gm) || []).length;
fertig({ datei, code, sek, einzeln, schlecht, aus });
if (abgewuergt) {
aus += `\n\n FEHL DIE PRUEFUNG HING. Nach ${Math.round(FRIST_MS / 1000)} s kam nichts mehr`
+ " -- abgebrochen.\n Letzte Ausgabe davor steht oben; dort ist sie stehengeblieben."
+ "\n Haeufigste Ursache: eine Verbindung, die nie endet (Ereignisstrom),"
+ "\n oder ein warten auf etwas, das nicht kommt.\n";
}
fertig({
datei, code: abgewuergt ? 124 : code, sek,
/* Die abgewuergte Pruefung zaehlt als fehlgeschlagen, nicht als
"keine Pruefung gelaufen" -- das sind zwei verschiedene Sachen,
und beide muessen unterscheidbar bleiben. */
einzeln: einzeln + (abgewuergt ? 1 : 0),
schlecht: schlecht + (abgewuergt ? 1 : 0),
aus, hing: abgewuergt,
});
});
});
+12 -1
View File
@@ -27,6 +27,11 @@
technik: 'Setups, Fehlerbilder und Lösungen pro Creator sauber dokumentiert.',
community: 'Moderation, Aktionen und Konflikte – ruhig statt Drama.',
schutz: 'Richtlinien, Vorfälle und was daraus gelernt wurde.',
/* Der Satz nennt die Trennung beim Namen, die das Konzept fordert:
Betreuung und Umsetzung sind unsere Seite, offizielle Wege sind
Agenturseite. Solange das nur im Kopf steht, wird es bei jedem
Streitfall neu verhandelt. */
agentur: 'Kampagnen, Schulungen und der offizielle Weg zur Agentur – mit Stand und Antwort.',
};
let ich = null;
@@ -203,7 +208,13 @@
Sinn ergibt: LIVE, Community und Technik. Bei Content sind es
eigene Ideen des Creators -- die bewertet niemand mit einer
Ampel. */
if (["live", "community", "technik", "schutz"].includes(bereich)
/* "agentur" ist seit dem 06.09.2026 dabei, und aus demselben
Grund wie die anderen vier: Ein Anliegen an die Agentur hat
einen STAND -- eingereicht, in Klärung, beantwortet. Genau
dafür ist die Ampel da, samt der Nachrichten daran. Ohne sie
wäre der "offizielle Weg" wieder nur eine Liste, in der etwas
steht und niemand weiß, wie es ausgegangen ist. */
if (["live", "community", "technik", "schutz", "agentur"].includes(bereich)
&& window.ampelAnhaengen && karten.size) {
const stand = await window.ampelnHolen([...karten.keys()]);
for (const [id, k] of karten) {
+26
View File
@@ -135,6 +135,13 @@
Bericht, sondern um einen VERLAUF. */
zahlen: [
'M4 16 9.5 11.5 13 14 20 6.5 20 19.5 4 19.5z'
],
/* Agentur (06.09.2026): ein Aktenkoffer. Bewusst kein
Haendedruck und kein Gebaeude -- beides wird bei 20 Pixeln zu
Matsch. Der Koffer ist die einzige Form, die man bei dieser
Groesse noch als "offizielle Seite" liest. */
agentur: [
'M3.5 8h17v11h-17z'
]
};
@@ -257,6 +264,15 @@
fuellung: ['M13.2 3.2 6 13.4h5.2l-.8 7.4 7.6-10.6h-5.2z'],
linien: ['M13.2 3.2 6 13.4h5.2l-.8 7.4 7.6-10.6h-5.2z'],
},
agentur: {
/* Der Deckel als Flaeche -- daran erkennt man einen Koffer, so wie
man einen Kalender an seinem Kopf erkennt. */
fuellung: ['M3.5 8h17v4.6h-17z'],
linien: ['M3.5 8h17v11h-17z',
'M9 8V6.4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1V8',
'M3.5 12.6h17',
'M10.9 11.6h2.2v2h-2.2z'],
},
zahlen: {
fuellung: ['M4 16 9.5 11.5 13 14 20 6.5 20 19.5 4 19.5z'],
/* Der Linienzug selbst, die Grundlinie darunter und ein Punkt am
@@ -368,6 +384,16 @@
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=technik', szene: 'garage' },
{ name: 'Schutz & Regeln', unter: 'Richtlinien und Vorfälle', zeichen: 'schutz', ton: 1,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=schutz', szene: 'halle' },
/* DER SECHSTE BEREICH (06.09.2026). Kernprinzip 04 des Konzepts
lautet "Die Agentur bleibt angebunden" -- und dafuer gab es
bis heute nichts. Fuenf Bereiche standen, dieser fehlte.
Steht bei den anderen fuenf und nicht unter "Team & System":
Es ist ein Bereich wie LIVE, Technik oder Schutz, nur dass
die andere Seite des Tisches die Agentur ist.
Ton 9 (Orange) -- deutlich von seinen Nachbarn 1 (Blau) und
14 (Gruen) entfernt. */
{ name: 'Agentur', unter: 'Kampagnen, Schulungen, offizieller Weg', zeichen: 'agentur', ton: 9,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=agentur', szene: 'halle' },
{ name: 'Reports & Review', unter: 'Woche und Entscheidung', zeichen: 'reports', ton: 14,
rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'report.html', szene: 'showbuehne' },
],
+2 -1
View File
@@ -15,7 +15,7 @@
const $ = (id) => document.getElementById(id);
const BEREICHSNAME = {
live: 'LIVE', content: 'Content', technik: 'Technik',
community: 'Community', schutz: 'Schutz',
community: 'Community', schutz: 'Schutz', agentur: 'Agentur',
};
let ich = null;
@@ -92,6 +92,7 @@
community: 'bereich.html?b=community',
technik: 'bereich.html?b=technik',
schutz: 'bereich.html?b=schutz',
agentur: 'bereich.html?b=agentur',
content: 'content.html',
};
const bereichZiel = (b, zeigen) => {
+8 -8
View File
@@ -9,9 +9,9 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
</head>
<body class="start">
@@ -211,10 +211,10 @@
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/aufgaben.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/aufgaben.js?v=202609062206" defer></script>
</body>
</html>
+9 -9
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/automation.css?v=202609062206" />
</head>
<body class="start">
@@ -134,10 +134,10 @@
</main>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/automation.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/automation.js?v=202609062206" defer></script>
</body>
</html>
+13 -13
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/bereich.css?v=202609062206" />
</head>
<body class="start">
@@ -117,14 +117,14 @@
</main>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/checkliste.js?v=202609061826" defer></script>
<script src="assets/js/ampel.js?v=202609061826" defer></script>
<script src="assets/js/schulung.js?v=202609061826" defer></script>
<script src="assets/js/vorlagen.js?v=202609061826" defer></script>
<script src="assets/js/bereich.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/checkliste.js?v=202609062206" defer></script>
<script src="assets/js/ampel.js?v=202609062206" defer></script>
<script src="assets/js/schulung.js?v=202609062206" defer></script>
<script src="assets/js/vorlagen.js?v=202609062206" defer></script>
<script src="assets/js/bereich.js?v=202609062206" defer></script>
</body>
</html>
+10 -10
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/calls.css?v=202609062206" />
</head>
<body class="start">
@@ -76,11 +76,11 @@
</main>
<script src="assets/js/ki.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/calls.js?v=202609061826" defer></script>
<script src="assets/js/ki.js?v=202609062206" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/calls.js?v=202609062206" defer></script>
</body>
</html>
+8 -8
View File
@@ -9,16 +9,16 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<!-- aufgaben.css bringt die Dialog- und Formularstile mit (.dialog,
.neu__zeile, .dialog__knoepfe). Sie stehen dort seit dem
Aufgabenbrett; sie hierher zu kopieren hiesse, sie an zwei Stellen
zu pflegen. Dieselbe Lösung nutzen kalender.html und content.html.
Gemeldet von pruef-css-klassen.mjs, bevor jemand einen ungestylten
Dialog zu sehen bekam. -->
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/chat.css?v=202609062206" />
</head>
<body class="start">
@@ -154,9 +154,9 @@
</form>
</dialog>
<script src="assets/js/bereiche.js?v=202609061826"></script>
<script src="assets/js/kopf.js?v=202609061826"></script>
<script src="assets/js/glocke.js?v=202609061826"></script>
<script src="assets/js/chat.js?v=202609061826"></script>
<script src="assets/js/bereiche.js?v=202609062206"></script>
<script src="assets/js/kopf.js?v=202609062206"></script>
<script src="assets/js/glocke.js?v=202609062206"></script>
<script src="assets/js/chat.js?v=202609062206"></script>
</body>
</html>
+11 -11
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/content.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/content.css?v=202609062206" />
</head>
<body class="start">
@@ -166,12 +166,12 @@
</main>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/checkliste.js?v=202609061826" defer></script>
<script src="assets/js/vorlagen.js?v=202609061826" defer></script>
<script src="assets/js/content.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/checkliste.js?v=202609062206" defer></script>
<script src="assets/js/vorlagen.js?v=202609062206" defer></script>
<script src="assets/js/content.js?v=202609062206" defer></script>
</body>
</html>
+9 -9
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/dateien.css?v=202609062206" />
</head>
<body class="start">
@@ -95,10 +95,10 @@
</main>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/dateien.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/dateien.js?v=202609062206" defer></script>
</body>
</html>
+2 -2
View File
@@ -27,7 +27,7 @@
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
</head>
<body class="gate">
@@ -145,6 +145,6 @@
</p>
</main>
<script src="assets/js/gate.js?v=202609061826" defer></script>
<script src="assets/js/gate.js?v=202609062206" defer></script>
</body>
</html>
+9 -9
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/kalender.css?v=202609062206" />
</head>
<body class="start">
@@ -206,10 +206,10 @@
</main>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/kalender.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/kalender.js?v=202609062206" defer></script>
</body>
</html>
+9 -9
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609062206" />
</head>
<body class="start">
@@ -199,10 +199,10 @@
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609061826"></script>
<script src="assets/js/bereiche.js?v=202609061826"></script>
<script src="assets/js/kopf.js?v=202609061826"></script>
<script src="assets/js/glocke.js?v=202609061826"></script>
<script src="assets/js/leistung.js?v=202609061826"></script>
<script src="assets/js/wahl.js?v=202609062206"></script>
<script src="assets/js/bereiche.js?v=202609062206"></script>
<script src="assets/js/kopf.js?v=202609062206"></script>
<script src="assets/js/glocke.js?v=202609062206"></script>
<script src="assets/js/leistung.js?v=202609062206"></script>
</body>
</html>
+9 -9
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/personen.css?v=202609062206" />
</head>
<body class="start">
@@ -123,10 +123,10 @@
</main>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/personen.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/personen.js?v=202609062206" defer></script>
</body>
</html>
+10 -10
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609062206" />
</head>
<body class="start">
@@ -201,11 +201,11 @@
</main>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/steckbrief.js?v=202609061826" defer></script>
<script src="assets/js/profil.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/steckbrief.js?v=202609062206" defer></script>
<script src="assets/js/profil.js?v=202609062206" defer></script>
</body>
</html>
+10 -10
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/report.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/report.css?v=202609062206" />
</head>
<body class="start">
@@ -93,11 +93,11 @@
</main>
<script src="assets/js/ki.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/report.js?v=202609061826" defer></script>
<script src="assets/js/ki.js?v=202609062206" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/report.js?v=202609062206" defer></script>
</body>
</html>
+9 -9
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/scouting.css?v=202609062206" />
</head>
<body class="start">
@@ -126,10 +126,10 @@
</div>
</div>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/scouting.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/scouting.js?v=202609062206" defer></script>
</body>
</html>
+7 -7
View File
@@ -9,8 +9,8 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
</head>
<body class="start">
@@ -160,10 +160,10 @@
</p>
</main>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/start.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/start.js?v=202609062206" defer></script>
</body>
</html>
+10 -10
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/startcheck.css?v=202609062206" />
</head>
<body class="start">
@@ -72,11 +72,11 @@
</main>
<script src="assets/js/ki.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/startcheck.js?v=202609061826" defer></script>
<script src="assets/js/ki.js?v=202609062206" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/startcheck.js?v=202609062206" defer></script>
</body>
</html>
+9 -9
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/profil.css?v=202609062206" />
</head>
<body class="start">
@@ -113,10 +113,10 @@
</main>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/steckbrief.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/steckbrief.js?v=202609062206" defer></script>
</body>
</html>
+7 -7
View File
@@ -9,9 +9,9 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/uebersicht.css?v=202609062206" />
</head>
<body class="start">
@@ -82,10 +82,10 @@
</main>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/uebersicht.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/uebersicht.js?v=202609062206" defer></script>
</body>
</html>
+9 -9
View File
@@ -9,10 +9,10 @@
<meta name="theme-color" content="#0674b9" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/start.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609061826" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/start.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609062206" />
<link rel="stylesheet" href="assets/css/wissen.css?v=202609062206" />
</head>
<body class="start">
@@ -177,10 +177,10 @@
</main>
<script src="assets/js/wahl.js?v=202609061826" defer></script>
<script src="assets/js/bereiche.js?v=202609061826" defer></script>
<script src="assets/js/kopf.js?v=202609061826" defer></script>
<script src="assets/js/glocke.js?v=202609061826" defer></script>
<script src="assets/js/wissen.js?v=202609061826" defer></script>
<script src="assets/js/wahl.js?v=202609062206" defer></script>
<script src="assets/js/bereiche.js?v=202609062206" defer></script>
<script src="assets/js/kopf.js?v=202609062206" defer></script>
<script src="assets/js/glocke.js?v=202609062206" defer></script>
<script src="assets/js/wissen.js?v=202609062206" defer></script>
</body>
</html>