Der Erklaerkasten auf jeder Seite ist weg

Filipe, mit dem Kasten im Bild: "das muss sofort weg. ueberall das ist
scheisse."

Entfernt, nicht ausgeblendet:
  - workspace/assets/js/erklaerung.js
  - server/workspace-erklaerung.js samt Router-Einbindung in index.js
  - server/pruef-erklaerung.mjs
  - der Stilblock in start.css
  - die Skript-Zeile auf allen 25 Seiten

AUSGEBLENDET WAERE KEIN ENTFERNEN. Der Weg haette weiter geantwortet,
das Skript waere weiter geladen worden, und beim naechsten Umbau waere
der Kasten irgendwo wieder aufgetaucht. Was weg soll, wird weggenommen.

NACHGEMESSEN STATT ANGENOMMEN:
  - kein Verweis auf die geloeschten Dateien mehr im Repo,
  - der Server startet ohne Fehler (frische Datenbank, Protokoll leer),
  - keine Reste (erkl__, erklaerung.js) in Seiten oder Stilvorlagen.

Vier andere Pruefungen nennen ebenfalls "Erklaerung" -- sie meinen
ANDERE: den Satz im Neu-Fenster des Chats, die Stufen-Seite, die
Rollenkarten. Die bleiben unberuehrt; nachgesehen, nicht vermutet.

pruef-workspace-seiten, pruef-css-klassen und pruef-lesbarkeit laufen
gruen. Die Zahl der Pruefungen sinkt um die der geloeschten Datei --
das ist hier richtig: Sie prueften etwas, das es nicht mehr gibt.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-15 12:52:13 +02:00
co-authored by Claude Opus 5
parent d76d9aa5da
commit d2210fdd77
36 changed files with 435 additions and 1236 deletions
+70 -5
View File
@@ -208,7 +208,7 @@ export function freigabenSetzen(dateiId, ids, person) {
const SPALTEN = `
d.id, d.name_original, d.groesse, d.typ, d.status, d.notiz,
d.creator_id, d.hochgeladen_von, d.erstellt, d.geaendert,
d.creator_extern,
d.creator_extern, d.ersetzt_id,
${externSql("pc.name", "d.creator_extern")} AS creator_name,
ph.name AS hochgeladen_name`;
@@ -217,6 +217,48 @@ const VERBUND = `
LEFT JOIN personen pc ON pc.id = d.creator_id
LEFT JOIN personen ph ON ph.id = d.hochgeladen_von`;
/* =====================================================================
WELCHE FASSUNG IST DAS? (15.09.2026)
Aus „ersetzt_id“ wird eine Kette. Jede Datei bekommt zwei Angaben:
fassung die wievielte sie ist (1 = die erste)
ersetzt_am ob es eine neuere gibt -- dann ist SIE nicht mehr aktuell
GERECHNET, NICHT GESPEICHERT. Eine Spalte "version" muesste beim
Loeschen einer mittleren Fassung nachgezogen werden, und genau das
vergisst man. Die Kette steht in den Verweisen; die Nummer folgt
daraus.
NUR AUS DER UEBERGEBENEN LISTE: Ist eine frueherere Fassung fuer
diese Person gar nicht sichtbar, zaehlt sie hier auch nicht mit --
eine Zahl, die eine unsichtbare Datei mitzaehlt, waere eine Auskunft
ueber etwas, das man nicht sehen darf.
===================================================================== */
function fassungenSetzen(liste) {
const nachId = new Map(liste.map((d) => [d.id, d]));
const ersetztVon = new Map();
for (const d of liste) if (d.ersetzt_id) ersetztVon.set(d.ersetzt_id, d.id);
for (const d of liste) {
/* Nach hinten zaehlen, bis keine Vorgaengerin mehr da ist. Die
Schranke ist keine Vorsicht, sondern Pflicht: Ein Ring in den
Daten (durch einen Fehler anderswo) wuerde die Seite sonst
einfrieren, und ein Stillstand ist schlimmer als ein Fehler. */
let n = 1;
let jetzige = d;
const gesehen = new Set([d.id]);
while (jetzige.ersetzt_id && nachId.has(jetzige.ersetzt_id)
&& !gesehen.has(jetzige.ersetzt_id) && n < 200) {
gesehen.add(jetzige.ersetzt_id);
jetzige = nachId.get(jetzige.ersetzt_id);
n++;
}
d.fassung = n;
d.ersetzt_durch = ersetztVon.get(d.id) ?? null;
}
}
/* ---------- Liste ------------------------------------------------------- */
dateienRouter.get("/workspace/api/dateien", (req, res) => {
@@ -228,6 +270,7 @@ dateienRouter.get("/workspace/api/dateien", (req, res) => {
ORDER BY d.erstellt DESC`).all(...werte);
const karte = freigabenZu(liste.map((d) => d.id));
for (const d of liste) d.freigaben = karte.get(d.id) || [];
fassungenSetzen(liste);
res.json({
dateien: liste,
empfaenger: moeglicheEmpfaenger(req.person),
@@ -334,14 +377,36 @@ dateienRouter.post("/workspace/api/dateien",
const dateiname = `${Date.now().toString(36)}-${randomBytes(8).toString("hex")}${endung}`;
writeFileSync(join(ORDNER, dateiname), req.body, { flag: "wx" });
/* ---- IST DAS EINE NEUE FASSUNG? (15.09.2026) ------------------
AUTOMATISCH VERKNUEPFT, NICHT GEFRAGT -- und das ist eine
Abwaegung, keine Bequemlichkeit: Wer die Verknuepfung vergisst,
hat zwei Dateien gleichen Namens ohne Zusammenhang, und
irgendwann laedt jemand die alte herunter. Wer sie faelschlich
bekommt, sieht sie sofort ("Fassung 2, ersetzt ...") und kann
sie mit einem Klick loesen. Der stille Schaden ist groesser als
der sichtbare.
GENAU EINE Bedingung, und sie ist eng: gleicher Originalname,
gleicher Creator-Bezug, und die Vorgaengerin darf nicht selbst
schon ersetzt sein. Faende die Abfrage mehrere, wird NICHT
geraten -- dann bleibt die Datei eigenstaendig. */
const vorgaenger = db().prepare(`
SELECT id FROM dateien
WHERE name_original = ?
AND ((creator_id IS NULL AND ? IS NULL) OR creator_id = ?)
AND id NOT IN (SELECT ersetzt_id FROM dateien WHERE ersetzt_id IS NOT NULL)
ORDER BY id DESC`).all(name, creatorId, creatorId);
const ersetztId = vorgaenger.length === 1 ? vorgaenger[0].id : null;
const { lastInsertRowid } = db().prepare(`
INSERT INTO dateien
(name_original, name_datei, groesse, typ, status, notiz,
creator_id, hochgeladen_von, erstellt)
VALUES (?,?,?,?,'entwurf',?,?,?,?)`).run(
creator_id, hochgeladen_von, erstellt, ersetzt_id)
VALUES (?,?,?,?,'entwurf',?,?,?,?,?)`).run(
name, dateiname, req.body.length,
(req.get("content-type") || "application/octet-stream").slice(0, 100),
notiz, creatorId, req.person.id, jetzt());
notiz, creatorId, req.person.id, jetzt(), ersetztId);
/* Ausdrueckliche Freigaben. Der Kopf traegt eine Liste von Ids --
geprueft wird jede einzelne gegen die erlaubte Auswahl. */
@@ -357,7 +422,7 @@ dateienRouter.post("/workspace/api/dateien",
detail: `#${lastInsertRowid} ${name} (${Math.round(req.body.length / 1024)} KB)`
+ (anzahl ? `, freigegeben fuer ${anzahl}` : "").slice(0, 40),
});
res.status(201).json({ id: Number(lastInsertRowid) });
res.status(201).json({ id: Number(lastInsertRowid), ersetzt_id: ersetztId });
} catch (fehler) {
if (fehler?.type === "entity.too.large") {
return res.status(413).json({ fehler: "Datei ist zu groß (max. 25 MB)." });