From 8c599d7961955d05420ef9425201bc19f1720285 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Thu, 24 Sep 2026 11:27:10 +0200 Subject: [PATCH] =?UTF-8?q?=E2=80=9EAn=20alle"=20bei=20=E2=80=9EAn=20wen"?= =?UTF-8?q?=20=E2=80=93=20und=20kein=20Creator=20mehr=20auf=20der=20Team-S?= =?UTF-8?q?eite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit === screen6: „An alle" === Filipe: „bei an ween fehlt noch die option alle neben den namen allen." Der Knopf steht VORN, nicht hinten: Wer etwas an das ganze Team geben will, soll nicht erst an sechs Namen vorbeilesen. Er traegt die Anzahl der Leute und faerbt sich wie die Stufe „Alle" -- beide bedeuten „keine Einschraenkung", und zwei Farben dafuer waeren zwei Aussagen fuer einen Gedanken. Ab zwei Personen; bei einer waere „alle" derselbe Handgriff wie ihr Name. EINE ANFRAGE, NICHT SECHS. Der Browser koennte fuer jede Person einzeln fragen -- und beim dritten von sechs Aufrufen die Verbindung verlieren. Dann haette die Haelfte des Teams die Aufgabe und die andere nicht, und niemand saehe, wo es abgebrochen ist. DER DUPLIKAT-SCHUTZ WANDERTE IN DIE SCHLEIFE. Er fragt, was EINE Person schon liegen hat; stuende er davor, bekaeme nur die erste ihre Pruefung und alle anderen die Vorlage doppelt -- genau so entstanden am 22.09. aus zwei Klicks 112 Aufgaben. Gemessen: erster Klick 36 Aufgaben (12 mal 3 Modis), zweiter Klick 0 angelegt und 36 uebersprungen. Ein gesperrter Modi bekommt nichts, ein Creator auch nicht, und wer gar nicht verteilen darf, kommt ueber „alle" ebenfalls nicht weiter. === screen4: kein anderer Creator === Filipe: „in dieser app gibt es keinen und wird es niemals einen anderen creator geben wie mich." Auf der Aufgabenseite war eine Filterreihe mit „Alle Creator" beschriftet -- und darunter standen Modis. Auf der Team-Adresse gibt es keine Creator und wird es nie geben. Sie heisst dort jetzt „Für wen". DAS HAUS KOMMT VOM SERVER. `/api/ich` schickt jetzt `haus`; es stand schon an der Sitzung und wurde nie mitgeschickt. Eine Rollenliste im Browser waere die naechste zweite Wahrheit -- und falsch fuer DogFather, der in beiden Haeusern arbeitet. NEUN WEITERE „BEFUNDE" WAREN MEINE MESSFEHLER. Meine erste Messung lief ueber 127.0.0.1 und meldete unter anderem „CREATOR WORKSPACE" in der Kopfleiste JEDER Seite. Auf der echten Adresse steht dort „Team Dogi" -- `person.haus` wird aus dem Host abgeleitet, und auf 127.0.0.1 ist das Haus nun einmal die Agentur. Nachgemessen mit gesetztem Host: crew. -> haus="crew", marke="Team Dogi". Die Uebersichtsseite mit ihren Creator-Texten steht auf crew. gar nicht erst in den Kacheln. Gemessen: pruef-an-alle, 19 Pruefungen, 0 Fehler. Gruen: pruef-aufgabenbrett, pruef-aufgaben-vorlagen, pruef-vorlagen, pruef-entwicklung (48), pruef-css-klassen. Co-Authored-By: Claude Opus 5 --- server/pruef-an-alle.mjs | 231 +++++++++++++++++++++++++++ server/workspace-vorlagen.js | 73 +++++++-- server/workspace.js | 17 ++ workspace/anruf-probe.html | 10 +- workspace/app.webmanifest | 8 +- workspace/assets/css/start.css | 12 ++ workspace/assets/js/aufgaben.js | 18 ++- workspace/assets/js/entwicklung.js | 29 +++- workspace/assets/js/vorlagenbrett.js | 29 ++++ workspace/aufgaben.html | 38 ++--- workspace/automation.html | 32 ++-- workspace/befinden.html | 34 ++-- workspace/bereich.html | 44 ++--- workspace/bewerben.html | 38 ++--- workspace/bewerbungen.html | 36 ++--- 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 | 36 ++--- workspace/hilfe.html | 30 ++-- 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 | 34 ++-- workspace/report.html | 36 ++--- workspace/scouting.html | 32 ++-- workspace/start.html | 32 ++-- workspace/startcheck.html | 34 ++-- workspace/steckbrief.html | 36 ++--- workspace/support.html | 28 ++-- workspace/talente.html | 36 ++--- workspace/team.html | 32 ++-- workspace/teamlage.html | 34 ++-- workspace/teilen.html | 30 ++-- workspace/treff-moderation.html | 32 ++-- workspace/treff-regeln.html | 32 ++-- workspace/uebersicht.html | 32 ++-- workspace/unsere-seiten.html | 30 ++-- workspace/werdegang.html | 32 ++-- workspace/willkommen.html | 30 ++-- workspace/wissen.html | 34 ++-- 47 files changed, 1004 insertions(+), 633 deletions(-) create mode 100644 server/pruef-an-alle.mjs diff --git a/server/pruef-an-alle.mjs b/server/pruef-an-alle.mjs new file mode 100644 index 00000000..58680c7e --- /dev/null +++ b/server/pruef-an-alle.mjs @@ -0,0 +1,231 @@ +/* ===================================================================== + „AN ALLE" BEI „AN WEN" (24.09.2026) + + Filipe: „bei an ween fehlt noch die option alle neben den namen + allen." + + DREI FRAGEN, und die dritte ist die, an der es schiefgehen kann: + + 1. Trifft „alle" wirklich JEDEN -- oder nur den ersten? + 2. Bleibt der Duplikat-Schutz erhalten? Er fragt, was EINE Person + schon hat; bei „alle" muss er je Person neu gestellt werden. + Sonst bekaeme nur die erste ihre Pruefung und alle anderen + die Vorlage doppelt -- genau so entstanden am 22.09. aus zwei + Klicks 112 Aufgaben. + 3. Kommt jemand darueber an Leute heran, die ihn nichts angehen? + „alle" darf nicht mehr treffen als die Namen daneben. + ===================================================================== */ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const ordner = mkdtempSync(join(tmpdir(), "ws-aa-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); + +import { notbremse } from "./helfer-notbremse.mjs"; +const { eigenerPort } = await import("./helfer-port.mjs"); +const PORT = await eigenerPort(import.meta, "die An-alle-Pruefung"); + +process.env.PORT = `${PORT}`; +process.env.SITE_ACCESS_SECRET = "lokaler-test"; +process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1"; +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(240_000, "pruef-an-alle"); +await new Promise((r) => setTimeout(r, 900)); + +let fehler = 0, geprueft = 0; +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; +const melde = (t) => console.log(t); + +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 ks = randomBytes(32).toString("hex"); +d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") + .run("code_kennung_schluessel", ks, jetzt); +function anlegen(name, rolle, code, aktiv = 1) { + 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 (?,?,?,?,?,?,?,?)") + .run(name, rolle, hash, salt, 32768, + createHmac("sha256", ks).update(code).digest("hex"), aktiv, jetzt); + return d.prepare("SELECT last_insert_rowid() AS id").get().id; +} +anlegen("Filipe", "admin", "CODE-DOGI-0001"); +const modis = [ + anlegen("Miss", "modi", "CODE-MODI-0001"), + anlegen("Ghost", "modi", "CODE-MODI-0002"), + anlegen("Marina", "modi", "CODE-MODI-0003"), +]; +/* EIN GESPERRTER MODI. Er darf NICHT mitbekommen -- ein Zugang, der + nicht hereinkommt, braucht keine Aufgabenliste. */ +const gesperrt = anlegen("Diene", "modi", "CODE-MODI-0004", 0); +/* Und ein Creator: Er steht gar nicht in dieser Auswahl und darf + auch ueber „alle" nichts bekommen. */ +const creator = anlegen("Luna", "creator", "CODE-CREA-0001"); +d.close(); + +const CREW = "crew.dogfather-universe.com"; +const { request } = await import("node:http"); +function roh(pfad, keks, koerper, methode) { + return new Promise((fertig, schief) => { + const daten = koerper ? JSON.stringify(koerper) : null; + const a = request({ + host: "127.0.0.1", port: PORT, path: pfad, + method: methode || (daten ? "POST" : "GET"), + headers: { + Host: CREW, Origin: `https://${CREW}`, + ...(keks ? { Cookie: keks } : {}), + ...(daten ? { "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(daten) } : {}), + }, + }, (antwort) => { + let text = ""; + antwort.on("data", (t) => { text += t; }); + antwort.on("end", () => fertig({ code: antwort.statusCode, text })); + }); + a.on("error", schief); + if (daten) a.write(daten); + a.end(); + }); +} +const json = (r) => { try { return JSON.parse(r.text); } catch { return {}; } }; + +async function anmelden(rolle, code) { + const r = await new Promise((fertig, schief) => { + const daten = JSON.stringify({ rolle, code, alter_ok: true }); + const a = request({ + host: "127.0.0.1", port: PORT, path: "/workspace/api/anmelden", + method: "POST", + headers: { Host: CREW, Origin: `https://${CREW}`, + "Content-Type": "application/json", "Content-Length": Buffer.byteLength(daten) }, + }, (antwort) => { + let text = ""; + antwort.on("data", (t) => { text += t; }); + antwort.on("end", () => fertig({ code: antwort.statusCode, kopf: antwort.headers })); + }); + a.on("error", schief); + a.write(daten); a.end(); + }); + if (r.code !== 200) return null; + return [].concat(r.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; "); +} + +const kDogi = await anmelden("admin", "CODE-DOGI-0001"); +const kModi = await anmelden("modi", "CODE-MODI-0001"); +ok(!!kDogi && !!kModi, "DogFather und ein Modi sind angemeldet"); + +/* Wie viele Aufgaben hat wer? Direkt aus der Datenbank -- die Antwort + der Route zu glauben hiesse, sie mit sich selbst zu pruefen. */ +function zaehle(personId) { + const db2 = new DatabaseSync(process.env.WORKSPACE_DB, { readOnly: true }); + const { n } = db2.prepare( + "SELECT COUNT(*) AS n FROM aufgaben WHERE verantwortlich_id = ? AND vorlage IS NOT NULL") + .get(personId); + db2.close(); + return n; +} + +/* Welche Kategorie gibt es ueberhaupt? Aus dem Katalog, den die Seite + auch bekommt -- eine erfundene Gruppe waere eine Pruefung, die + nichts trifft. */ +const katalog = json(await roh("/workspace/api/vorlagen", kDogi)); +const ersteGruppe = (katalog.katalog?.gruppen || [])[0]?.schluessel + || (katalog.katalog?.aufgaben || [])[0]?.gruppe || ""; +ok(!!ersteGruppe, `es gibt eine Kategorie zum Verteilen (${ersteGruppe})`); + +melde("\n=== 1. alle trifft jeden ==="); +{ + const vorher = modis.map(zaehle); + ok(vorher.every((x) => x === 0), `vorher hat niemand etwas (${vorher.join(", ")})`); + + const a = await roh("/workspace/api/vorlagen/uebernehmen", kDogi, + { art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle" }); + ok(a.code === 200, `die Anfrage geht durch (${a.code})`); + const r = json(a); + ok((r.angelegt || 0) > 0, `es wurde etwas angelegt (${r.angelegt})`); + + const nachher = modis.map(zaehle); + ok(nachher.every((x) => x > 0), + `JEDER der drei Modis hat jetzt Aufgaben (${nachher.join(", ")})`); + ok(new Set(nachher).size === 1, + "und alle gleich viele -- sonst hat die Schleife jemanden ausgelassen"); + ok((r.angelegt || 0) === nachher[0] * modis.length, + `die Zahl in der Antwort passt zur Datenbank (${r.angelegt} = ${nachher[0]} x ${modis.length})`); +} + +melde("\n=== 2. Der Duplikat-Schutz gilt je Person ==="); +{ + const vorher = modis.map(zaehle); + const a = await roh("/workspace/api/vorlagen/uebernehmen", kDogi, + { art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle" }); + ok(a.code === 200, `ein zweiter Klick geht durch (${a.code})`); + const r = json(a); + ok((r.angelegt || 0) === 0, + `und legt NICHTS mehr an (${r.angelegt}) -- genau das war der Fehler vom 22.09.`); + /* `uebersprungen` ist eine ZAHL, kein Feld -- nachgesehen in + workspace-vorlagen.js (`uebersprungen: uebersprungen.length`). + Mein erster Versuch rief `.length` darauf auf und bekam + `undefined`; ein Messfehler, der wie eine fehlende Antwort + aussah. */ + ok(Number(r.uebersprungen) > 0, + `stattdessen sagt die Antwort, wie viele schon lagen (${r.uebersprungen})`); + ok(Number(r.uebersprungen) === 36, + `und zwar alle 36 -- 12 Vorlagen mal 3 Modis (${r.uebersprungen})`); + const nachher = modis.map(zaehle); + ok(nachher.join() === vorher.join(), + `niemand hat etwas doppelt (${nachher.join(", ")})`); +} + +melde("\n=== 3. alle trifft nicht mehr als die Namen ==="); +{ + ok(zaehle(gesperrt) === 0, + `ein GESPERRTER Modi bekommt nichts (${zaehle(gesperrt)})`); + ok(zaehle(creator) === 0, + `ein Creator erst recht nicht (${zaehle(creator)})`); + + /* Und wer gar nicht verteilen darf, kommt auch ueber „alle" nicht + weiter -- sonst waere der neue Wert ein Weg an der Rechtefrage + vorbei. */ + const alsModi = await roh("/workspace/api/vorlagen/uebernehmen", kModi, + { art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle" }); + ok(alsModi.code >= 400, + `ein Modi kann nicht an alle verteilen (${alsModi.code})`); +} + +melde("\n=== 4. Gegenproben ==="); +{ + /* EINE EINZELNE PERSON GEHT WEITERHIN -- sonst hiesse „alle + funktioniert" nur, dass der alte Weg kaputt ist. */ + const einzeln = await roh("/workspace/api/vorlagen/uebernehmen", kDogi, + { art: "katalog", nr: 0, gruppe: ersteGruppe, person_id: modis[0] }); + ok(einzeln.code === 200 || einzeln.code === 400, + `der Weg an EINE Person antwortet noch (${einzeln.code})`); + + /* Ein erfundener Wert ist kein "alle". */ + const quatsch = await roh("/workspace/api/vorlagen/uebernehmen", kDogi, + { art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle-zusammen" }); + ok(quatsch.code >= 400, + `alle-zusammen ist nicht alle (${quatsch.code})`); + + /* Und ohne Person weiterhin die alte Absage. */ + const ohne = await roh("/workspace/api/vorlagen/uebernehmen", kDogi, + { art: "katalog_alle", gruppe: ersteGruppe, stufe: "" }); + ok(ohne.code === 400, `ohne Person immer noch 400 (${ohne.code})`); +} + +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } + +console.log(""); +console.log(`${geprueft} Pruefungen, ${fehler} Fehler`); +console.log(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG"); +process.exit(fehler ? 1 : 0); diff --git a/server/workspace-vorlagen.js b/server/workspace-vorlagen.js index 320e028a..d6509583 100644 --- a/server/workspace-vorlagen.js +++ b/server/workspace-vorlagen.js @@ -1903,16 +1903,44 @@ vorlagenRouter.post("/workspace/api/vorlagen/uebernehmen", gleicheHerkunft, if (!darfAufgabenVerteilen(req.person)) { return res.status(403).json({ fehler: "erst_bewerben" }); } - const zielId = Number(req.body.person_id) || null; - if (!zielId) return res.status(400).json({ fehler: "Bitte zuerst eine Person wählen." }); + /* ==== „AN ALLE" (24.09.2026) ================================ + + Filipe: „bei an ween fehlt noch die option alle neben den + namen allen." + + EINE ANFRAGE, NICHT SECHS. Der Browser koennte fuer jede + Person einzeln anfragen -- und beim dritten von sechs + Aufrufen die Verbindung verlieren. Dann haette die Haelfte + des Teams die Aufgabe und die andere nicht, und niemand + saehe, wo es abgebrochen ist. Hier laeuft alles in einem + Durchgang. + + DER DUPLIKAT-SCHUTZ GILT JE PERSON. Er steht weiter unten + und fragt, was diese eine Person schon hat -- bei „alle" + wird er deshalb pro Person neu gestellt. Wer die Vorlage + schon liegen hat, bekommt sie nicht doppelt; genau so + entstanden am 22.09. die 112 Aufgaben aus zwei Klicks. */ + const anAlle = String(req.body.person_id) === "alle"; + const zielId = anAlle ? null : (Number(req.body.person_id) || null); + if (!anAlle && !zielId) { + return res.status(400).json({ fehler: "Bitte zuerst eine Person wählen." }); + } /* Nur an jemanden, fuer den die Kategorien ueberhaupt gelten -- sonst landete eine Modi-Aufgabe samt Kategorie an einem Creator, und die Trennung waere von innen aufgeweicht. */ - const zielPerson = db().prepare( - "SELECT id, rolle FROM personen WHERE id = ? AND aktiv = 1").get(zielId); - if (!zielPerson || zielPerson.rolle !== "modi") { + const zielListe = anAlle + ? db().prepare( + "SELECT id, rolle FROM personen WHERE aktiv = 1 AND rolle = 'modi' ORDER BY id").all() + : [db().prepare( + "SELECT id, rolle FROM personen WHERE id = ? AND aktiv = 1").get(zielId)] + .filter(Boolean); + if (!zielListe.length || zielListe.some((z) => z.rolle !== "modi")) { return res.status(404).json({ fehler: "nicht_gefunden" }); } + /* Nur noch als Beleg, dass die Liste nicht leer ist -- gelesen + wird sie in der Schleife weiter unten. Ein ungenutzter Wert + waere Rauschen; dieser sagt etwas. */ + if (!zielListe[0]) return res.status(404).json({ fehler: "nicht_gefunden" }); /* AUS DEM KATALOG, DEN DIE SEITE AUCH BEKOMMEN HAT (16.09.2026). @@ -1961,19 +1989,30 @@ vorlagenRouter.post("/workspace/api/vorlagen/uebernehmen", gleicheHerkunft, Dieselbe Sperre steht seit dem 10.09. im Termin-Zweig weiter unten. Dass sie hier fehlte, ist nicht aufgefallen, weil niemand zweimal drueckt -- bis es jemand tat. */ - const liegtSchon = art === "katalog_alle" - ? new Set(db().prepare( - `SELECT vorlage FROM aufgaben - WHERE verantwortlich_id = ? - AND vorlage IS NOT NULL - AND status NOT IN ('erledigt', 'abgebrochen')`) - .all(zielPerson.id).map((z) => z.vorlage)) - : new Set(); + /* JE PERSON EINMAL (24.09.2026, fuer „An alle"). + + Die Duplikatsperre fragt, was DIESE Person schon liegen + hat -- deshalb steht sie INNERHALB der Schleife. Waere sie + davor, bekaeme bei „alle" nur die erste Person ihre + Pruefung, und alle anderen haetten die Vorlage doppelt. + + Bei einer einzelnen Person aendert sich nichts: Die + Schleife laeuft dann genau einmal. */ const lagSchonDa = []; - for (const v of auswahl) { - if (liegtSchon.has(v.schluessel)) { lagSchonDa.push(v.titel); continue; } - katalogAufgabeAnlegen(v, zielPerson.id, req.person.id); - angelegt.push(v.titel); + for (const ziel of zielListe) { + const liegtSchon = art === "katalog_alle" + ? new Set(db().prepare( + `SELECT vorlage FROM aufgaben + WHERE verantwortlich_id = ? + AND vorlage IS NOT NULL + AND status NOT IN ('erledigt', 'abgebrochen')`) + .all(ziel.id).map((z) => z.vorlage)) + : new Set(); + for (const v of auswahl) { + if (liegtSchon.has(v.schluessel)) { lagSchonDa.push(v.titel); continue; } + katalogAufgabeAnlegen(v, ziel.id, req.person.id); + angelegt.push(v.titel); + } } /* DIE ZAHL GEHOERT IN DIE ANTWORT, nicht nur in die Datenbank. Ein Knopf, der "12 uebernehmen" heisst und dann schweigend diff --git a/server/workspace.js b/server/workspace.js index 28f7108a..16f4c144 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -6268,6 +6268,23 @@ workspaceRouter.get("/workspace/api/ich", (req, res) => { weiterarbeiten. Fuer DogFather und die rechte Hand ist sie nicht leer, fuer alle anderen schon. */ darf_zugaenge_verwalten: darfAnlegen(person).length > 0, + /* IN WELCHEM HAUS BIN ICH GERADE? (24.09.2026) + + Filipe: „in dieser app gibt es keinen und wird es niemals einen + anderen creator geben wie mich." + + Die Aufgabenseite beschriftete eine Filterreihe mit „Alle + Creator" -- und auf der Team-Dogi-Adresse standen darunter + Modis. Es gibt dort keine Creator, und es wird nie welche + geben. Die Oberflaeche konnte das bisher nicht wissen: `haus` + steht an der Sitzung, wurde aber nie mitgeschickt. + + ABGELEITET, NICHT GERATEN. Eine Rollenliste im Browser + („hand, linke, modi heisst crew") waere die naechste zweite + Wahrheit -- und sie waere falsch fuer DogFather, der in beiden + Haeusern arbeitet. Hier entscheidet dieselbe Angabe, die auch + `hausBedingung` und `bereicheFuer` benutzen. */ + haus: person?.haus === "crew" ? "crew" : "agentur", /* UND LOESCHEN -- getrennt, weil es das Einzige ist, was sich nicht zuruecknehmen laesst. Heute dieselbe Antwort; sollte Filipe es spaeter wieder einschraenken, ist hier die Stelle. */ diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index ff5ee714..c44395e4 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`. --> - - + + - +