From 189d0b3363e9f33802f0da502c4120c6f5be035b Mon Sep 17 00:00:00 2001 From: Dogfather Date: Tue, 22 Sep 2026 23:47:52 +0200 Subject: [PATCH] B1: Das Anschlagbrett haelt 24 Stunden -- und sieht aus wie ein Anschlagbrett MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "die sachen sollen in der seite vom anschlagbrett immer automatisch nach 24h von da verschwinden. weil alles hat seine kategorie und was nicht ist auch nicht um ewig auf der seite zu bleiben fertig." SEINE BEGRUENDUNG IST DIE REGEL. Ein Anschlagbrett sagt, was GERADE gilt. Was laenger gilt, hat seinen eigenen Bereich -- Regeln stehen bei den Regeln, Termine bei "Was ansteht". Eine Ansage vom letzten Dienstag, die noch haengt, ist keine Ansage mehr, sondern Papier an der Wand. DIE DREI ENTSCHEIDUNGEN, DIE ICH TREFFEN MUSSTE -- und woran ich sie festgemacht habe: 1. AB WANN LAUFEN DIE 24 STUNDEN? Ab `erstellt`, nicht ab `datum`. `datum` ist ein vom Team gesetztes Feld und kann in der Zukunft liegen (es traegt die Termine). Eine Ansage, die morgen gilt, soll morgen verschwinden und nicht uebermorgen. 2. VERSCHWINDET SIE GANZ? Nein. Er sagt "von DA verschwinden" -- von der Seite. Geloescht wird nichts: Wer eine Ansage geschrieben hat, soll sie wiederfinden. Sie kommt als `abgehaengt` mit und steht zugeklappt unter einer leisen Zeile "Abgehaengt (3)". Auf dem Brett selbst steht sie damit nicht mehr. 3. WER ENTSCHEIDET? Der Server. Die Stunden im Browser nachzurechnen waere eine zweite Fassung derselben Regel, und eine davon waere irgendwann aelter. Die Frist geht als Zahl mit der Antwort. DAS AUSSEHEN ("viel besser gestaltet, übersichtlicher, moderner, hochwertiger, professioneller"): Was haengt, sieht jetzt aus wie etwas, das haengt -- kraeftiger Rand links in der Farbe seiner ART (Ansage babyblau, Neue Regel gold, Hinweis lila, Danke gruen), ein Schein von links, mehr Luft. Die Art stand bisher nur im Text; jetzt sieht man sie. Abgehaengtes ist grau statt durchsichtig -- durchsichtig hiesse, das Buehnenbild scheint durch, also Text auf einem Foto. pruef-anschlagbrett.mjs (NEU, 12/0) misst beide Seiten der Grenze (23 h haengt, 25 h nicht), rechnet relativ zur jetzigen Uhrzeit statt mit einem festen Datum, und hat den Eintrag, an dem sich alles entscheidet: alt aufgehaengt, Datum von heute. Wer nach `datum` filtert, laesst ihn haengen. Dazu zwei Gegenproben: auf jedem anderen Brett gibt es das Merkmal gar nicht, und wo die Grenze WIRKLICH liegt, wird aus den Eintraegen nachgerechnet statt aus der Zahl im Modul. NEBENBEFUND, den erst dieser Commit ausgeloest hat --------------------------------------------------- pruef-bereiche-lesend stuerzte ab: "listen EACCES 127.0.0.1:5040". Die Portnummern werden aus der Stelle im Alphabet abgeleitet -- heute sind fuenf neue Pruefdateien dazugekommen, und dadurch ist diese Pruefung auf die 5040 gerutscht. Die haelt auf diesem Rechner ein Windows-Dienst (svchost, PID 7136). `portMussFreiSein` kannte nur ZWEI Antworten: EADDRINUSE oder frei. EACCES galt als frei -- die Pruefung lief weiter, bis der echte Server auf demselben Port startete und mit einem unbehandelten Fehler abstuerzte. Ein Stapelauszug aus node:net, der wie ein Fehler im Code aussieht. Jetzt drei Antworten. Und ein belegter Port wird anders behandelt als ein gesperrter: Belegt geht vorbei (der andere Lauf hoert auf), gesperrt nie. Abzubrechen hiesse, die Pruefung waere auf diesem Rechner dauerhaft nicht ausfuehrbar -- die Sorte Warnung, die immer kommt und deshalb weggeklickt wird. Ausgewichen wird um einen festen Betrag (+4000), damit die Nummer abgeleitet und eindeutig bleibt. Mitgelaufen und gruen: pruef-bereiche-lesend, pruef-css-klassen. Co-Authored-By: Claude Opus 5 --- server/helfer-port.mjs | 74 +++++++++- server/pruef-anschlagbrett.mjs | 230 +++++++++++++++++++++++++++++++ server/workspace-bereiche.js | 64 +++++++++ workspace/anruf-probe.html | 10 +- workspace/app.webmanifest | 8 +- workspace/assets/css/bereich.css | 74 ++++++++++ workspace/assets/js/bereich.js | 76 ++++++++++ workspace/aufgaben.html | 36 ++--- workspace/automation.html | 32 ++--- workspace/befinden.html | 32 ++--- workspace/bereich.html | 44 +++--- workspace/bewerben.html | 36 ++--- workspace/bewerbungen.html | 34 ++--- workspace/calls.html | 34 ++--- workspace/chat.html | 38 ++--- workspace/content.html | 34 ++--- workspace/crew-index.html | 12 +- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 34 ++--- workspace/entwicklung.html | 32 ++--- workspace/hilfe.html | 28 ++-- workspace/index.html | 10 +- workspace/kalender.html | 34 ++--- workspace/leistung.html | 32 ++--- workspace/material.html | 32 ++--- workspace/personen.html | 32 ++--- workspace/profil.html | 36 ++--- workspace/rechte.html | 32 ++--- workspace/report.html | 36 ++--- workspace/scouting.html | 32 ++--- workspace/start.html | 32 ++--- workspace/startcheck.html | 34 ++--- workspace/steckbrief.html | 36 ++--- workspace/talente.html | 34 ++--- workspace/team.html | 32 ++--- workspace/teamlage.html | 32 ++--- workspace/teilen.html | 28 ++-- workspace/treff-moderation.html | 30 ++-- workspace/treff-regeln.html | 30 ++-- workspace/uebersicht.html | 32 ++--- workspace/unsere-seiten.html | 28 ++-- workspace/werdegang.html | 30 ++-- workspace/willkommen.html | 30 ++-- workspace/wissen.html | 34 ++--- 44 files changed, 1097 insertions(+), 591 deletions(-) create mode 100644 server/pruef-anschlagbrett.mjs diff --git a/server/helfer-port.mjs b/server/helfer-port.mjs index 4abf8e1e..f8b7d9a8 100644 --- a/server/helfer-port.mjs +++ b/server/helfer-port.mjs @@ -34,16 +34,72 @@ import { createServer } from "node:net"; -/** Wirft, wenn der Port belegt ist. Sonst gibt er ihn frei zurück. */ -export async function portMussFreiSein(port, wer = "diese Pruefung") { - const belegt = await new Promise((fertig) => { +/** Was ist mit diesem Port los? + * + * DREI ANTWORTEN, NICHT ZWEI (22.09.2026) -- und die dritte hat + * heute gefehlt: + * + * null frei + * EADDRINUSE jemand hoert darauf (meist ein zweiter Prueflauf) + * EACCES das System gibt ihn nicht her -- dauerhaft + * + * Vorher wurde NUR auf EADDRINUSE geprueft; jeder andere Fehler galt + * als "frei". Auf diesem Rechner haelt ein Windows-Dienst (svchost, + * PID 7136) den Port 5040. `createServer().listen` scheiterte dort mit + * EACCES, die Antwort war trotzdem "frei", und die Pruefung lief + * weiter -- bis der ECHTE Server auf demselben Port startete und mit + * einem unbehandelten Fehler abstuerzte. Eine Meldung, die "belegt" + * heissen muesste, kam nie; stattdessen ein Stapelauszug aus + * node:net, der wie ein Fehler im Code aussieht. + * + * Aufgefallen ist es, weil die Nummern sich verschoben haben: Sie + * werden aus der Stelle im Alphabet abgeleitet, und heute sind fuenf + * neue Pruefdateien dazugekommen. Der Fall lag also seit Langem da + * und wartete nur darauf, dass jemand auf die 5040 rutscht. */ +async function portLage(port) { + return new Promise((fertig) => { const s = createServer(); - s.once("error", (f) => fertig(f.code === "EADDRINUSE")); - s.once("listening", () => s.close(() => fertig(false))); + s.once("error", (f) => fertig(f.code || "FEHLER")); + s.once("listening", () => s.close(() => fertig(null))); s.listen(port, "127.0.0.1"); }); +} - if (belegt) { +/** Wirft, wenn der Port belegt ist. Sonst gibt er ihn frei zurück. */ +export async function portMussFreiSein(port, wer = "diese Pruefung") { + const lage = await portLage(port); + + /* DAUERHAFT GESPERRT -> AUSWEICHEN, NICHT ABBRECHEN. + ------------------------------------------------------------------ + Ein belegter Port ist ein Zustand, der vorbeigeht: Der andere Lauf + hoert auf, und danach geht es. Ein vom System reservierter Port + geht NIE. Abzubrechen hiesse, diese Pruefung waere auf diesem + Rechner dauerhaft nicht ausfuehrbar -- und das waere die Sorte + Warnung, die immer kommt und deshalb weggeklickt wird. + + Ausgewichen wird um einen festen Betrag, nicht auf "den naechsten + freien": Damit bleibt die Nummer ABGELEITET und eindeutig. Zwei + Pruefungen, die beide ausweichen, landen auf zwei verschiedenen + Nummern, weil ihre Ausgangsnummern verschieden waren. */ + if (lage === "EACCES") { + const ersatz = port + AUSWEICHEN; + const lage2 = await portLage(ersatz); + if (lage2 === null) { + console.log(`[Port] ${port} ist vom System belegt (EACCES) --` + + ` ${wer} nimmt ${ersatz}.`); + return ersatz; + } + console.error(`\nABBRUCH: Port ${port} ist vom System gesperrt, und der`); + console.error(` Ausweichport ${ersatz} ist ebenfalls nicht frei (${lage2}).\n`); + console.error(` ${wer} kann hier nicht messen. Das ist KEIN Befund am Code --`); + console.error(` es heisst nur, dass gerade niemand nachsehen konnte.\n`); + console.error(` Nachsehen, wer die Ports haelt:`); + console.error(` netstat -ano | findstr :${port}`); + console.error(` netstat -ano | findstr :${ersatz}\n`); + process.exit(3); + } + + if (lage !== null) { console.error(`\nABBRUCH: Port ${port} ist belegt.\n`); console.error(` ${wer} startet einen eigenen Server auf diesem Port. Laeuft dort`); console.error(` schon etwas, wuerde gegen einen FREMDEN Stand gemessen -- mit`); @@ -124,6 +180,12 @@ import { basename, dirname } from "node:path"; * werden: "irgendwo ueber 4400" haette plausibel geklungen. */ const BASIS = 5000; const PRO_DATEI = 2; +/** Um wie viel ausgewichen wird, wenn das System einen Port nicht + * hergibt. 4000 liegt sicher oberhalb des ganzen abgeleiteten + * Bereichs (BASIS bis BASIS+5000 in Schritten von 2, bei derzeit + * rund 180 Dateien also bis etwa 5400) -- zwei Pruefungen koennen + * sich dadurch nicht ins Gehege kommen, auch wenn beide ausweichen. */ +const AUSWEICHEN = 4000; /** Der Port dieser Datei -- abgeleitet, nicht vergeben. * diff --git a/server/pruef-anschlagbrett.mjs b/server/pruef-anschlagbrett.mjs new file mode 100644 index 00000000..8ad13f02 --- /dev/null +++ b/server/pruef-anschlagbrett.mjs @@ -0,0 +1,230 @@ +/* ===================================================================== + DAS ANSCHLAGBRETT HÄLT 24 STUNDEN + ===================================================================== + + Filipe am 22.09.2026: + + „die sachen sollen in der seite vom anschlagbrett immer + automatisch nach 24h von da verschwinden. weil alles hat seine + kategorie und was nicht ist auch nicht um ewig auf der seite zu + bleiben fertig." + + SEINE BEGRÜNDUNG IST DIE REGEL. Ein Anschlagbrett sagt, was GERADE + gilt. Was länger gilt, hat seinen eigenen Bereich — Regeln stehen + bei den Regeln, Termine bei „Was ansteht". Eine Ansage vom letzten + Dienstag, die noch hängt, ist keine Ansage mehr, sondern Papier an + der Wand. + + --------------------------------------------------------------------- + DREI DINGE, DIE EINE ZEITREGEL LEICHT FALSCH MACHT + + 1. DIE UHR ALS ANNAHME. Eine Prüfung, die „vor 25 Stunden" mit einem + festen Datum nachbaut, ist grün, bis der Kalender sie überholt. + Hier wird deshalb relativ gerechnet (`Date.now() - n * Stunde`) — + dieselbe Lehre wie bei `gate-oeffnung.mjs` im Shop. + + 2. DIE FALSCHE SPALTE. `datum` ist ein vom Team gesetztes Feld und + kann in der Zukunft liegen (es trägt die Termine). Gezählt wird + ab `erstellt`. Gemessen wird das hier mit einem Eintrag, dessen + beide Daten AUSEINANDERLIEGEN — sonst wäre nicht zu sehen, + welches der beiden zählt. + + 3. DIE GRENZE SELBST. Bei genau 24 Stunden darf es nicht kippen, + ohne dass jemand es merkt. Geprüft werden deshalb beide Seiten: + kurz davor hängt sie, kurz danach nicht. + + Und der wichtigste Punkt: NICHTS WIRD GELÖSCHT. Er sagt „von DA + verschwinden" — von der Seite. Wer eine Ansage geschrieben hat, soll + sie wiederfinden. + ===================================================================== */ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { notbremse } from "./helfer-notbremse.mjs"; +import { eigenerPort } from "./helfer-port.mjs"; + +const ordner = mkdtempSync(join(tmpdir(), "ws-anschlag-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +const PORT = await eigenerPort(import.meta, "pruef-anschlagbrett"); +process.env.PORT = `${PORT}`; +process.env.SITE_ACCESS_SECRET = "lokaler-test"; +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 }); }; +await import("./index.js"); +notbremse(90_000, "pruef-anschlagbrett"); +await new Promise((r) => setTimeout(r, 800)); + +let fehler = 0, geprueft = 0, offen = 0; +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; +const unklar = (t) => { offen++; console.log(" -- " + t); }; + +const { ANSCHLAG_STUNDEN } = await import("./workspace-bereiche.js"); +const STUNDE = 3600 * 1000; + +/* ---------- Leute und Einträge ---------------------------------------- */ +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); +const d = new DatabaseSync(process.env.WORKSPACE_DB); +const jetzt = new Date().toISOString(); +const schluessel = randomBytes(32).toString("hex"); +d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") + .run("code_kennung_schluessel", schluessel, jetzt); + +function anlegen(name, rolle, code) { + const salt = randomBytes(16).toString("hex"); + const hash = scryptSync(code, salt, 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, code_kennung, aktiv, erstellt)" + + " VALUES (?,?,?,?,?,?,1,?)") + .run(name, rolle, hash, salt, 32768, + createHmac("sha256", schluessel).update(code).digest("hex"), jetzt); + return d.prepare("SELECT last_insert_rowid() AS id").get().id; +} +const idChef = anlegen("Filipe", "admin", "CODE-DOGI-0001"); + +/* RELATIV ZUR JETZIGEN UHRZEIT, nie mit einem festen Datum. */ +const vorStunden = (n) => new Date(Date.now() - n * STUNDE).toISOString(); + +/** Ein Aushang mit getrenntem `erstellt` und `datum`. */ +function aushang(titel, stundenAlt, datumStundenAlt = stundenAlt) { + d.prepare(`INSERT INTO eintraege (bereich, art, titel, text, datum, status, erstellt, erstellt_von) + VALUES ('anschlag', 'ansage', ?, ?, ?, 'offen', ?, ?)`) + .run(titel, "Inhalt", vorStunden(datumStundenAlt).slice(0, 10), + vorStunden(stundenAlt), idChef); + return d.prepare("SELECT last_insert_rowid() AS id").get().id; +} + +const idFrisch = aushang("Gerade aufgehängt", 0.5); +const idKurzDavor = aushang("Kurz vor der Grenze", ANSCHLAG_STUNDEN - 1); +const idKurzDanach = aushang("Kurz nach der Grenze", ANSCHLAG_STUNDEN + 1); +const idUralt = aushang("Von letzter Woche", 24 * 7); +/* DER ENTSCHEIDENDE: `erstellt` ist alt, `datum` ist von heute. Wer + nach `datum` filtert, lässt ihn hängen -- und genau das wäre der + Fehler, den man sonst nie sieht. */ +const idAltMitNeuemDatum = aushang("Alt aufgehängt, Datum von heute", ANSCHLAG_STUNDEN + 3, 0); +d.close(); + +/* ---------- Anfragen --------------------------------------------------- */ +const { request: httpAnfrage } = await import("node:http"); +const CREW = "crew.dogfather-universe.com"; +function anfrage(pfad, { methode = "GET", keks = "", rumpf = null } = {}) { + return new Promise((fertig, schief) => { + const daten = rumpf === null ? null : Buffer.from(JSON.stringify(rumpf)); + const a = httpAnfrage({ + host: "127.0.0.1", port: PORT, path: pfad, method: methode, + headers: { + Host: CREW, Origin: `https://${CREW}`, Referer: `https://${CREW}/workspace/`, + ...(daten ? { "Content-Type": "application/json", "Content-Length": daten.length } : {}), + ...(keks ? { Cookie: keks } : {}), + }, + }, (antwort) => { + const st = []; + antwort.on("data", (x) => st.push(x)); + antwort.on("end", () => { + const text = Buffer.concat(st).toString("utf8"); + let json = null; try { json = JSON.parse(text); } catch { /* kein JSON */ } + fertig({ code: antwort.statusCode, json, + keks: [].concat(antwort.headers["set-cookie"] || []) + .map((c) => c.split(";")[0]).join("; ") }); + }); + }); + a.on("error", schief); + if (daten) a.write(daten); + a.end(); + }); +} +const keks = (await anfrage("/workspace/api/anmelden", + { methode: "POST", rumpf: { rolle: "admin", code: "CODE-DOGI-0001" } })).keks; + +console.log("=== Das Anschlagbrett haelt 24 Stunden ==="); +console.log(""); +console.log(`--- Die Frist steht an EINER Stelle: ${ANSCHLAG_STUNDEN} Stunden ---`); + +const a = await anfrage("/workspace/api/bereich/anschlag", { keks }); +ok(a.code === 200, `das Brett antwortet (${a.code})`); +const eintraege = a.json?.eintraege || []; +const nach = new Map(eintraege.map((e) => [e.id, e])); + +/* NICHTS IST VERSCHWUNDEN. Alle fünf kommen mit -- was fehlt, wäre + gelöscht, und das war ausdrücklich nicht gemeint. */ +ok(eintraege.length === 5, + `alle fünf Aushänge kommen mit (${eintraege.length}) — abgehängt ist nicht gelöscht`); + +ok(nach.get(idFrisch)?.abgehaengt === false, "was gerade aufgehängt wurde, hängt"); +ok(nach.get(idKurzDavor)?.abgehaengt === false, + `eine Stunde vor der Grenze hängt sie noch (${ANSCHLAG_STUNDEN - 1} h alt)`); +ok(nach.get(idKurzDanach)?.abgehaengt === true, + `eine Stunde danach nicht mehr (${ANSCHLAG_STUNDEN + 1} h alt)`); +ok(nach.get(idUralt)?.abgehaengt === true, "die von letzter Woche erst recht nicht"); + +/* DIE WICHTIGSTE ZEILE. */ +ok(nach.get(idAltMitNeuemDatum)?.abgehaengt === true, + "gezählt wird ab `erstellt`, nicht ab `datum` — ein alter Aushang mit" + + " heutigem Datum hängt NICHT weiter"); + +/* DIE FRIST GEHT AN DIE SEITE MIT. */ +ok(a.json?.anschlag_stunden === ANSCHLAG_STUNDEN, + `die Frist kommt mit der Antwort (${a.json?.anschlag_stunden})`); + +/* ===================================================================== + GEGENPROBE: NUR DAS ANSCHLAGBRETT + ===================================================================== + Eine Regel, die versehentlich überall gilt, wäre schlimmer als + keine: Auf den Regeln, bei „Was ansteht" und auf jedem anderen Brett + soll nichts nach einem Tag verschwinden. Und `abgehaengt` darf dort + gar nicht erst auftauchen -- ein Feld, das immer `false` sagt, wird + irgendwann von jemandem ausgewertet. + ===================================================================== */ +console.log(""); +console.log("--- Gegenprobe: nur hier, sonst nirgends ---"); + +{ + const dd = new DatabaseSync(process.env.WORKSPACE_DB); + dd.prepare(`INSERT INTO eintraege (bereich, art, titel, text, datum, status, erstellt, erstellt_von) + VALUES ('regeln', 'regel', ?, ?, ?, 'offen', ?, ?)`) + .run("Uralte Regel", "Inhalt", vorStunden(24 * 30).slice(0, 10), vorStunden(24 * 30), idChef); + dd.close(); + + const b = await anfrage("/workspace/api/bereich/regeln", { keks }); + const regeln = b.json?.eintraege || []; + ok(regeln.length >= 1, `das Regelbrett hat ${regeln.length} Eintrag/Einträge`); + ok(regeln.length >= 1 && regeln.every((e) => e.abgehaengt === undefined), + "dort gibt es das Merkmal gar nicht — die Regel gilt nur am Anschlagbrett"); + ok(b.json?.anschlag_stunden === undefined, + "und die Frist steht dort auch nicht in der Antwort"); +} + +/* ===================================================================== + GEGENPROBE ZUR MESSUNG: WÜRDE SIE EINEN FEHLER SEHEN? + ===================================================================== + Bis hierhin könnte alles auch dann grün sein, wenn `abgehaengt` + schlicht dem Alter folgt und die Grenze woanders läge. Also einmal + nachrechnen, wo sie wirklich liegt -- gemessen an den Einträgen + selbst, nicht an der Zahl aus dem Modul. + ===================================================================== */ +console.log(""); +console.log("--- Wo die Grenze wirklich liegt ---"); +{ + const alter = (e) => (Date.now() - Date.parse(e.erstellt)) / STUNDE; + const haengend = eintraege.filter((e) => !e.abgehaengt).map(alter); + const ab = eintraege.filter((e) => e.abgehaengt).map(alter); + const aeltesteHaengend = Math.max(...haengend); + const juengsteAb = Math.min(...ab); + ok(haengend.length > 0 && ab.length > 0 + && aeltesteHaengend < ANSCHLAG_STUNDEN && juengsteAb > ANSCHLAG_STUNDEN, + `die Grenze liegt zwischen ${aeltesteHaengend.toFixed(1)} h (hängt noch)` + + ` und ${juengsteAb.toFixed(1)} h (abgehängt) — also bei ${ANSCHLAG_STUNDEN}`); +} + +/* ---------- Ende ------------------------------------------------------ */ +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Windows haelt manchmal */ } + +console.log(""); +console.log(`${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"}` + + ` -- ${geprueft} geprueft, ${fehler} Fehler, ${offen} nicht nachsehbar`); +process.exit(fehler === 0 ? 0 : 1); diff --git a/server/workspace-bereiche.js b/server/workspace-bereiche.js index 87bbc0a8..44a11a03 100644 --- a/server/workspace-bereiche.js +++ b/server/workspace-bereiche.js @@ -80,6 +80,18 @@ export const bereicheRouter = express.Router(); /* Die Bereiche und ihre Arten. Bewusst hier und nicht in der Datenbank: Es sind Festlegungen aus dem Konzept, keine Nutzdaten. */ +/** Wie lange eine Ansage am Anschlagbrett haengt -- in Stunden. + * + * Filipe am 22.09.2026: „die sachen sollen in der seite vom + * anschlagbrett immer automatisch nach 24h von da verschwinden." + * + * ALS ZAHL AN EINER STELLE, damit die Anzeige („seit gestern") und + * die Regel (was noch haengt) nicht auseinanderlaufen koennen. Sie + * geht mit der Antwort an den Browser -- eine 24 im Skript waere die + * zweite Fassung, und beim naechsten Mal stuende dort eine andere + * Zahl als hier. */ +export const ANSCHLAG_STUNDEN = 24; + export const BEREICHE = { /* ===================================================================== DIE SIEBEN BRETTER DES TREFFS (11.09.2026) @@ -1303,6 +1315,52 @@ bereicheRouter.get("/workspace/api/bereich/:bereich", (req, res) => { ORDER BY ${reihenfolge}`) .all(...(treffBrett ? [wer] : []), ...regel.werte, bereich); + /* ================================================================= + DAS ANSCHLAGBRETT HAELT 24 STUNDEN (22.09.2026) + ================================================================= + + Filipe: „die sachen sollen in der seite vom anschlagbrett immer + automatisch nach 24h von da verschwinden. weil alles hat seine + kategorie und was nicht ist auch nicht um ewig auf der seite zu + bleiben fertig." + + SEINE BEGRUENDUNG IST DIE REGEL: Ein Anschlagbrett sagt, was + GERADE gilt. Was laenger gilt, hat seinen eigenen Bereich -- die + Regeln stehen bei den Regeln, Termine bei „Was ansteht". Eine + Ansage vom letzten Dienstag, die noch haengt, ist keine Ansage + mehr, sondern Papier an der Wand. + + AB WANN DIE 24 STUNDEN LAUFEN: ab `erstellt`, also ab dem + Augenblick, in dem jemand sie aufgehaengt hat. Nicht ab `datum` + -- das ist ein vom Team gesetztes Feld und kann in der Zukunft + liegen (es traegt die Termine). Eine Ansage, die morgen gilt, + soll morgen verschwinden und nicht uebermorgen. + + SIE VERSCHWINDET VON DER SEITE, NICHT AUS DER WELT. + Filipe sagt „von da verschwinden" -- von DA. Geloescht wird + nichts: Wer eine Ansage geschrieben hat, soll sie + wiederfinden, und eine Ansage rueckwirkend verschwinden zu + lassen waere etwas anderes als sie abzuhaengen. Die aelteren + kommen deshalb mit, aber als `abgehaengt` gekennzeichnet; die + Seite zeigt sie zugeklappt unter einer Zeile. + + WARUM DER SERVER DAS ENTSCHEIDET UND NICHT DER BROWSER: Im + Browser waere es eine zweite Fassung derselben Regel -- und die + eine wuerde irgendwann etwas anderes sagen als die andere. + Dieselbe Ueberlegung steht zehn Zeilen tiefer schon einmal, zum + Termin. + ================================================================= */ + if (bereich === "anschlag") { + const grenze = Date.now() - ANSCHLAG_STUNDEN * 3600 * 1000; + for (const e of eintraege) { + const auf = Date.parse(e.erstellt || ""); + /* OHNE LESBARES DATUM BLEIBT SIE HAENGEN. Ein unlesbarer + Zeitstempel ist ein Grund nachzusehen, kein Grund, etwas + verschwinden zu lassen. */ + e.abgehaengt = Number.isFinite(auf) ? auf < grenze : false; + } + } + /* STEHT DIESER ZETTEL IM KALENDER? (17.09.2026) Der Kalender zeigt seit heute, was auf den Brettern steht und @@ -1518,6 +1576,12 @@ bereicheRouter.get("/workspace/api/bereich/:bereich", (req, res) => { res.json({ bereich, einstellung: { ...einstellung, ober, entscheidung }, treff: treffBrett, gehoert, weiter, + /* WIE LANGE EINE ANSAGE HAENGT -- vom Server, damit die Anzeige + („seit gestern abgehaengt") und die Regel dieselbe Zahl + benutzen. Nur am Anschlagbrett; ueberall sonst gibt es keine + Frist, und ein Feld, das immer 24 sagt, wuerde irgendwann + irgendwo ausgewertet. */ + ...(bereich === "anschlag" ? { anschlag_stunden: ANSCHLAG_STUNDEN } : {}), /* DARF DIESER MENSCH IN DEN KALENDER? Beantwortet von den zwei Stellen, die es entscheiden -- nicht von einer dritten Regel im Browser. Die Karte zeigt ihren Termin dann als Text statt diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index a3069908..1af70638 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,15 +16,15 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +