47 fertige Inhalte statt acht leerer Bretter

Filipe, mit drei Bildschirmfotos nebeneinander -- Der Treff, das
Anschlagbrett, Was ansteht, alle drei mit demselben grauen Kasten:

  "ich meine alle kategorien sehen einfach so scheisse und leer aus,
   ich will fertige sachen schon drin wo auch die community und alle
   anderen benutzen koennen"

Er hat recht, und das ist mein Fehler: Es stand in MEINEM Plan, §5.1
-- "Acht leere Seiten sind schlimmer als keine." Ich habe es
aufgeschrieben und danach fuenf Stufen lang Technik gebaut.

Dreimal derselbe Satz auf drei Bildschirmen sieht nicht nach "neu" aus,
sondern nach defekt.

WAS JETZT BEREITLIEGT (47 Eintraege)
  Regeln & Hilfe   15   das Nachschlagewerk: sechs Regeln, drei
                        Folgen-Erklaerungen, drei Hilfen, drei Fragen
  Mitmachen        11   der Weg in drei Schritten, vier Aufgabengebiete,
                        vier ehrliche Fragen ("Was habe ich davon?")
  Der Treff         6   Gespraechsanfaenge, auf die man in einem Satz
                        antworten kann
  Anschlagbrett     5   Willkommen, Ablauf, wo was hingehoert, Danke
  Wunschliste       4   Beispiele, die zeigen, wie ein guter Wunsch
                        aussieht
  Was ansteht       4   Terminvorlagen
  Highlights        2   was hier hingehoert

NICHT AUTOMATISCH BEIM START. Ein Text, den niemand gelesen hat,
stuende sonst als Ansage des Teams auf einer oeffentlichen Seite.
"Bleibt fair" klingt harmlos, bis jemand fragt, was das im Streitfall
heisst -- und dann muss dahinterstehen, WER es gemeint hat. Uebernehmen
ist eine Entscheidung; automatisch fuellen waere eine Behauptung. Und
jeder Text laesst sich vorher aendern.

ZWEI BEREICHE SIND KEIN KATALOG, SONDERN FERTIGE SEITEN: "Regeln &
Hilfe" und "Mitmachen" sind Nachschlagewerke, keine Feeds. Ein leeres
Regelwerk ist schlimmer als gar keins -- im Streitfall hat dann niemand
etwas in der Hand. Die Regeln sagen ausserdem jeweils, was PASSIERT,
nicht nur was verboten ist, und die Hilfe nennt die Telefonseelsorge.

UND JEDER BEREICH SAGT JETZT SEINEN EIGENEN SATZ, wenn er leer ist:
"Gerade gilt nichts Besonderes. Das ist eine gute Nachricht." /
"Noch hat niemand Hallo gesagt. Sei der Erste -- ein Satz reicht."

DIE FALLE, DIE ICH MIR SELBST GEBAUT HATTE: Die Bremse aus Stufe 4
zaehlt zehn Beitraege je Stunde. Wer fuenfzehn Regeln uebernimmt,
haette nach der zehnten eine halb gefuellte Seite und die Meldung, er
habe zu viel geschrieben -- genau die Sorte Sicherung, die das richtige
Verhalten bestraft und deshalb abgeschaltet wird. Das Uebernehmen ist
eine einzelne Handlung des Teams und geht nicht durch die Bremse; die
Pruefung weist beides nach.

NEU: pruef-treff-start (27 Pruefungen)
Der Katalog selbst (jede Art gibt es im Bereich, jeder Text sagt mehr
als sein Titel, kein Titel doppelt, echte Umlaute), das Uebernehmen
(einzeln, alle, zweimal verdoppelt nichts), die Verborgenheit, die
sieben verschiedenen Leertexte -- und im Browser: elf Vorschlaege,
ein Klick, elf Eintraege, Katalog verschwindet.

pruef-treff 66, pruef-bremse 16, pruef-deutsche-texte 12,
pruef-bereiche-lesend, pruef-css-klassen: gruen.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-17 11:51:15 +02:00
co-authored by Claude Opus 5
parent 4b14906371
commit 7701f5e5e5
35 changed files with 1230 additions and 360 deletions
+112
View File
@@ -15,6 +15,7 @@
===================================================================== */
import express from "express";
import { TREFF_START } from "./workspace-treff-start.js";
import {
db, protokolliere, echteIp, sitzungLesen, betreutWo, darfCreator, betreuteIds, istLeitung, istDogFather, siehtAlles, istSpicy, ohneDogFather,
externPruefen, externSql,
@@ -55,6 +56,9 @@ export const BEREICHE = {
anschlag: {
name: "Anschlagbrett",
/* Was dasteht, wenn nichts dasteht. Ein Bereich, der
schweigt, sieht kaputt aus statt leer. */
leer: "Gerade gilt nichts Besonderes. Das ist eine gute Nachricht.",
ober: "Vom Team",
arten: {
ansage: "Ansage",
@@ -70,6 +74,9 @@ export const BEREICHE = {
ansteht: {
name: "Was ansteht",
/* Was dasteht, wenn nichts dasteht. Ein Bereich, der
schweigt, sieht kaputt aus statt leer. */
leer: "Noch kein Termin eingetragen. Der nächste Stream steht hier, sobald er feststeht.",
ober: "Termine",
/* AUSDRUECKLICH NICHT AN DEN ECHTEN KALENDER GEKOPPELT.
@@ -93,6 +100,9 @@ export const BEREICHE = {
treff: {
name: "Der Treff",
/* Was dasteht, wenn nichts dasteht. Ein Bereich, der
schweigt, sieht kaputt aus statt leer. */
leer: "Noch hat niemand Hallo gesagt. Sei der Erste — ein Satz reicht.",
ober: "Euer Raum",
/* DIE REIHENFOLGE IST ABSICHT. Das erste Feld bestimmt, was
geschrieben wird -- wer ein Formular oeffnet, dessen erste
@@ -117,6 +127,9 @@ export const BEREICHE = {
wunsch: {
name: "Wunschliste",
/* Was dasteht, wenn nichts dasteht. Ein Bereich, der
schweigt, sieht kaputt aus statt leer. */
leer: "Noch kein Wunsch. Was würdest du im nächsten Stream gern sehen?",
ober: "Was ihr euch wünscht",
/* DER WEG EINES WUNSCHES (17.09.2026).
@@ -165,6 +178,9 @@ export const BEREICHE = {
highlight: {
name: "Highlights",
/* Was dasteht, wenn nichts dasteht. Ein Bereich, der
schweigt, sieht kaputt aus statt leer. */
leer: "Hier kommen eure Clips und Bilder hin. Der erste darf ruhig wackelig sein.",
ober: "Von euch",
arten: {
clip: "Clip",
@@ -180,6 +196,9 @@ export const BEREICHE = {
regeln: {
name: "Regeln & Hilfe",
/* Was dasteht, wenn nichts dasteht. Ein Bereich, der
schweigt, sieht kaputt aus statt leer. */
leer: "Hier stehen die Regeln des Treffs — sobald sie eingetragen sind.",
ober: "Wie es hier läuft",
/* REGELN IN EINFACHER SPRACHE UND AN ECHTEN FAELLEN. Vage Regeln
erzeugen Grenzfaelle, und die muss dann jeder Modi einzeln
@@ -199,6 +218,9 @@ export const BEREICHE = {
mitmachen: {
name: "Mitmachen",
/* Was dasteht, wenn nichts dasteht. Ein Bereich, der
schweigt, sieht kaputt aus statt leer. */
leer: "Hier steht, wie man dazugehören kann — sobald es eingetragen ist.",
ober: "Nachwuchs",
/* DIE BRUECKE ZU DEN TALENTEN. Wer sich hier meldet, ist ein
Kandidat -- und Kandidaten gehoeren in die Kategorie, die es
@@ -1467,6 +1489,96 @@ function pruefe(bereich, körper, { neu }) {
return { aus, fehler };
}
/* =====================================================================
DER STARTKATALOG (17.09.2026)
Filipe, mit drei Bildschirmfotos leerer Bretter: "ich will fertige
sachen schon drin wo auch die community und alle anderen benutzen
koennen". Es stand sogar im Plan (§5.1): "Acht leere Seiten sind
schlimmer als keine."
NICHT AUTOMATISCH BEIM START. Ein Text, den niemand gelesen hat,
stuende sonst als Ansage des Teams auf einer oeffentlichen Seite.
"Bleibt fair" klingt harmlos, bis jemand fragt, was das im
Streitfall heisst -- und dann muss dahinterstehen, WER es gemeint
hat. Uebernehmen ist eine Entscheidung; automatisch fuellen waere
eine Behauptung.
UND DIE BREMSE GILT HIER NICHT. Sie zaehlt Beitraege pro Stunde und
wuerde beim Uebernehmen von fuenfzehn Regeln nach dem zehnten
abbrechen -- mit einer halb gefuellten Seite und der Meldung, man
habe zu viel geschrieben. Das ist genau die Sorte Sicherung, die das
richtige Verhalten bestraft und deshalb abgeschaltet wird. Der Weg
hier ist eine EINZELNE Handlung des Teams, kein Schreibrausch. */
bereicheRouter.get("/workspace/api/bereich/:bereich/start", (req, res) => {
try {
const bereich = String(req.params.bereich);
if (!BEREICHE[bereich]) return res.status(404).json({ fehler: "nicht_gefunden" });
/* Wer hier nicht schreiben darf, bekommt den Katalog gar nicht --
statt ihn zu bekommen und den Knopf ausgeblendet zu sehen. */
if (darfSchreiben(req.person, bereich)) return res.status(404).json({ fehler: "nicht_gefunden" });
const liste = TREFF_START[bereich] || [];
/* Was schon dasteht, kommt nicht noch einmal. Verglichen wird ueber
den Titel: Eine eigene Kennung an der Zeile waere eine zweite
Wahrheit, und beim Umformulieren liefe sie auseinander. */
const da = new Set(db().prepare(
"SELECT titel FROM eintraege WHERE bereich = ?").all(bereich).map((z) => z.titel));
res.json({
vorschlaege: liste.map((v, nr) => ({ nr, ...v, schon: da.has(v.titel) })),
offen: liste.filter((v) => !da.has(v.titel)).length,
});
} catch (fehler) {
console.error("[workspace] Startkatalog:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
bereicheRouter.post("/workspace/api/bereich/:bereich/start", gleicheHerkunft, (req, res) => {
try {
const bereich = String(req.params.bereich);
const einstellung = BEREICHE[bereich];
if (!einstellung) return res.status(404).json({ fehler: "nicht_gefunden" });
const warum = darfSchreiben(req.person, bereich);
if (warum) return res.status(403).json({ fehler: warum });
const liste = TREFF_START[bereich] || [];
if (!liste.length) return res.status(404).json({ fehler: "nicht_gefunden" });
const nr = req.body?.nr;
const gewaehlt = nr === undefined || nr === null || nr === ""
? liste
: [liste[Number(nr)]].filter(Boolean);
if (!gewaehlt.length) return res.status(400).json({ fehler: "Unbekannter Vorschlag." });
const da = new Set(db().prepare(
"SELECT titel FROM eintraege WHERE bereich = ?").all(bereich).map((z) => z.titel));
const neu = gewaehlt.filter((v) => !da.has(v.titel));
let angelegt = 0;
for (const v of neu) {
/* DIE ART MUSS ZUM BEREICH PASSEN. Sie steht im Katalog, aber
geprueft wird sie trotzdem -- sonst traegt ein Tippfehler im
Katalog eine Art in die Datenbank, die es dort nicht gibt, und
die Zeile laesst sich hinterher nicht mehr aendern. */
if (!Object.hasOwn(einstellung.arten, v.art)) continue;
db().prepare(`
INSERT INTO eintraege (bereich, art, titel, text, datum, status, erstellt, erstellt_von)
VALUES (?,?,?,?,?, 'offen', ?, ?)`)
.run(bereich, v.art, v.titel, v.text ?? null,
jetzt().slice(0, 10), jetzt(), req.person.id);
angelegt++;
}
protokolliere("bereich_start_uebernommen", {
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
detail: `${bereich} ${angelegt} von ${gewaehlt.length}`,
});
res.status(201).json({ angelegt, schon: gewaehlt.length - angelegt });
} catch (fehler) {
console.error("[workspace] Startkatalog uebernehmen:", fehler?.message);
res.status(503).json({ fehler: "nicht_verfuegbar" });
}
});
/* ---------- Anlegen ------------------------------------------------------ */
bereicheRouter.post("/workspace/api/bereich/:bereich", gleicheHerkunft, (req, res) => {