diff --git a/server/pruef-haus-seiten.mjs b/server/pruef-haus-seiten.mjs new file mode 100644 index 00000000..9529ab3e --- /dev/null +++ b/server/pruef-haus-seiten.mjs @@ -0,0 +1,270 @@ +/* ===================================================================== + Auf der Adresse von Team Dogi gibt es die Agenturseiten nicht + (15.09.2026) + + Filipe, mit dem Bildschirmfoto des Start-Checks auf crew.: + „das gibt es in der app nicht, dass ist nur auf der workspace app + aber nicht hier." + + --------------------------------------------------------------------- + ES WAR GRÖSSER ALS DIESE EINE SEITE + + Die Haustrennung entscheidet, WAS jemand sieht — Daten und Kacheln. + Sie entschied nie, welche SEITEN es auf einer Adresse gibt. Die + Rechtetafel kennt nur Rollen, keine Adressen. Zwischen beidem lag + das Loch: zehn Agenturseiten waren auf crew. erreichbar, drei davon + sogar für einen Modi. + + --------------------------------------------------------------------- + UND DIE GEGENPROBE IST HIER DAS WICHTIGSTE + + Eine Regel, die Seiten wegnimmt, muss beweisen, dass sie die + RICHTIGEN wegnimmt — und vor allem, dass auf der Agenturadresse + nichts fehlt. Sonst hätte ich Filipe eine Seite repariert und ihm + zehn andere genommen, ohne es zu merken. + + Aufrufen mit: node server/pruef-haus-seiten.mjs + ===================================================================== */ + +import { mkdtempSync, rmSync, readdirSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { request as httpAnfrage } from "node:http"; +import { portMussFreiSein } from "./helfer-port.mjs"; + +const PORT = await portMussFreiSein(4422, "pruef-haus-seiten"); + +const ordner = mkdtempSync(join(tmpdir(), "ws-haus-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +process.env.PORT = String(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 }); }; +import { notbremse } from "./helfer-notbremse.mjs"; +await import("./index.js"); +notbremse(240_000, "pruef-haus-seiten"); +await new Promise((r) => setTimeout(r, 700)); + +process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); }); +process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); }); + +let fehler = 0, geprueft = 0; +const melde = (t) => console.log(t); +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; + +const CREW = "crew.dogfather-universe.com"; +const HAUS = "workspace.dogfather-universe.com"; + +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); +const ws = await import("./workspace.js"); +const rechte = await import("./rechte.js"); + +const jetzt = new Date().toISOString(); +{ + const d = new DatabaseSync(process.env.WORKSPACE_DB); + const s = randomBytes(32).toString("hex"); + d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)" + + " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING") + .run("code_kennung_schluessel", s, jetzt); + const 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", s).update(code).digest("hex"), jetzt); + }; + anlegen("Filipe", "admin", "CODE-DOGI-0001"); + anlegen("Rieke", "hand", "CODE-HAND-0001"); + anlegen("Frida", "modi", "CODE-MODI-0001"); + anlegen("Mara", "manager", "CODE-MANA-0001"); + d.close(); +} + +function ruf(pfad, host, methode, kopf = {}, koerper = null) { + return new Promise((fertig, schief) => { + const a = httpAnfrage({ host: "127.0.0.1", port: PORT, path: pfad, method: methode, + headers: { Host: host, ...(koerper ? { "Content-Type": "application/json" } : {}), ...kopf } }, + (antwort) => { + let text = ""; + antwort.on("data", (s) => { text += s; }); + antwort.on("end", () => fertig({ + code: antwort.statusCode, text, ort: antwort.headers.location || "", kopf: antwort.headers })); + }); + a.on("error", schief); + if (koerper) a.write(JSON.stringify(koerper)); + a.end(); + }); +} +async function anmelden(rolle, code, host) { + const r = await ruf("/workspace/api/anmelden", host, "POST", {}, { rolle, code }); + return [].concat(r.kopf?.["set-cookie"] || []).map((c) => c.split(";")[0]).join("; "); +} +const kDogi = await anmelden("admin", "CODE-DOGI-0001", HAUS); +const kDogiCrew = await anmelden("admin", "CODE-DOGI-0001", CREW); +const kHand = await anmelden("hand", "CODE-HAND-0001", CREW); +const kModi = await anmelden("modi", "CODE-MODI-0001", CREW); +const kMana = await anmelden("manager", "CODE-MANA-0001", HAUS); +ok(!!kDogi && !!kDogiCrew && !!kHand && !!kModi && !!kMana, + "vier Menschen sind angemeldet, DogFather auf beiden Adressen"); + +/** Kommt man auf diese Seite? 200 ja, 302 nein. */ +const seite = async (datei, host, keks) => + ruf("/workspace/" + datei, host, "GET", { cookie: keks }); + +const SEITEN = readdirSync(new URL("../workspace/", import.meta.url)) + .filter((n) => n.endsWith(".html")); + +/* ======================================================================= + 1. DIE GEMELDETE SEITE + ======================================================================= */ +melde(""); +melde("=== 1. Der Start-Check ==="); +{ + const a = await seite("startcheck.html", CREW, kDogiCrew); + ok(a.code === 302, `auf der Team-Adresse gibt es ihn nicht (${a.code})`); + ok(/start\.html$/.test(a.ort), `man landet auf der Startseite (${a.ort})`); + + /* DIE GEGENPROBE, OHNE DIE DIE ZEILE DARÜBER NICHTS BEWEIST: + Auf der Agenturadresse ist der Start-Check unverändert da. */ + const b = await seite("startcheck.html", HAUS, kDogi); + ok(b.code === 200, `auf der Agenturadresse ist er da (${b.code})`); + ok(/Start-Check/.test(b.text), "und es ist wirklich die Seite"); + + /* UND EIN MODI KAM DA AUCH HIN -- das war der unangenehmste Teil. */ + const m = await seite("startcheck.html", CREW, kModi); + ok(m.code === 302, `auch für einen Modi ist er weg (${m.code})`); +} + +/* ======================================================================= + 2. ALLE AGENTURSEITEN, NICHT NUR DIESE EINE + ======================================================================= */ +melde(""); +melde("=== 2. Die übrigen neun ==="); +{ + const AGENTUR = ["automation.html", "calls.html", "content.html", "leistung.html", + "profil.html", "report.html", "scouting.html", "team.html", "uebersicht.html"]; + let zu = 0; + for (const s of AGENTUR) { + const a = await seite(s, CREW, kDogiCrew); + if (a.code === 302) zu++; else ok(false, `${s} ist auf crew. noch offen (${a.code})`); + } + ok(zu === AGENTUR.length, `alle ${zu} Agenturseiten sind auf der Team-Adresse zu`); + + /* GEGENPROBE: auf der Agenturadresse ist jede einzelne noch da. */ + let offen = 0; + for (const s of AGENTUR) { + const a = await seite(s, HAUS, kDogi); + if (a.code === 200) offen++; else ok(false, `${s} fehlt jetzt auf workspace. (${a.code})`); + } + ok(offen === AGENTUR.length, + `und auf der Agenturadresse sind alle ${offen} unverändert da`); + + /* UND FÜR EINE MANAGERIN, die es dort auch nur dort gibt. */ + const mana = await seite("scouting.html", HAUS, kMana); + ok(mana.code === 200, `eine Managerin kommt weiterhin ins Scouting (${mana.code})`); +} + +/* ======================================================================= + 3. WAS ZUM TEAM GEHÖRT, BLEIBT + ======================================================================= */ +melde(""); +melde("=== 3. Und was bleibt ==="); +{ + const TEAM = ["start.html", "aufgaben.html", "chat.html", "dateien.html", + "kalender.html", "wissen.html", "bereich.html", "entwicklung.html", + "befinden.html", "talente.html", "personen.html", "teamlage.html", + "treff-regeln.html", "treff-moderation.html", "rechte.html"]; + let da = 0; + for (const s of TEAM) { + const a = await seite(s, CREW, kDogiCrew); + if (a.code === 200) da++; else ok(false, `${s} ist auf crew. verschwunden (${a.code})`); + } + ok(da === TEAM.length, `alle ${da} Seiten des Teams sind da`); + + /* EIN MODI BEHÄLT SEINE. Besonders die beiden, die keine Kachel + haben und trotzdem ihm gehören. */ + for (const s of ["entwicklung.html", "befinden.html", "treff-regeln.html", "aufgaben.html"]) { + const a = await seite(s, CREW, kModi); + ok(a.code === 200, `ein Modi kommt weiterhin auf ${s} (${a.code})`); + } + /* Aber nicht auf die der Leitung -- das war schon vorher so und + muss es bleiben. */ + const verwehrt = await seite("personen.html", CREW, kModi); + ok(verwehrt.code === 302, `und nicht auf personen.html (${verwehrt.code})`); +} + +/* ======================================================================= + 4. DIE REGEL SELBST + ======================================================================= */ +melde(""); +melde("=== 4. Die Regel ==="); +{ + /* SIE IST ABGELEITET, NICHT GEPFLEGT: Was eine Kachel hat, ist + erreichbar. Gemessen statt behauptet. */ + const person = { id: 1, rolle: "modi", haus: "crew" }; + const kacheln = [...(ws.bereicheFuer(person) || []), ...(ws.zusatzBereicheFuer(person) || [])] + .map((k) => "/workspace/" + String(k.ziel || "").split("?")[0]); + const alle = kacheln.every((p) => ws.gehoertAufDieseAdresse(person, p)); + ok(alle, `alle ${kacheln.length} Kacheln eines Modis führen auf erreichbare Seiten`); + + /* EINE NEUE SEITE IST AUF crew. AUTOMATISCH ZU -- das ist die + Richtung, in die diese Regel altern soll. */ + ok(!ws.gehoertAufDieseAdresse(person, "/workspace/gibt-es-noch-nicht.html"), + "eine Seite, die es noch nicht gibt, ist dort automatisch zu"); + + /* UND AUF DER AGENTURADRESSE ÄNDERT SIE GAR NICHTS. */ + const agentur = { id: 2, rolle: "creator", haus: null }; + ok(SEITEN.every((s) => ws.gehoertAufDieseAdresse(agentur, "/workspace/" + s)), + `im anderen Haus lässt sie alle ${SEITEN.length} Seiten durch`); + + /* SIE ÄNDERT KEINE RECHTE. Was jemand darf, steht weiter allein in + rechte.js -- die Tafel ist unberührt. */ + ok((rechte.tafelJetzt()["/workspace/startcheck.html"] || []).includes("modi"), + "in der Rechtetafel steht der Start-Check unverändert"); + ok((await seite("startcheck.html", CREW, kModi)).code === 302, + "erreichbar ist er auf crew. trotzdem nicht — zwei verschiedene Fragen"); +} + +/* ======================================================================= + 5. KEINE SEITE FÄLLT ZWISCHEN DIE STÜHLE + ======================================================================= */ +melde(""); +melde("=== 5. Der Durchgang über alle Seiten ==="); +{ + /* JEDE .html EINZELN, für jede der drei Rollen des Teams. Es geht + nicht darum, ob sie offen oder zu ist -- sondern darum, dass + KEINE davon eine Antwort gibt, die weder 200 noch 302 ist. Ein + 500 an dieser Stelle wäre die Regel, die an einer Seite stolpert. */ + let gezaehlt = 0, seltsam = 0; + const zu = { admin: [], hand: [], modi: [] }; + for (const [rolle, keks] of [["admin", kDogiCrew], ["hand", kHand], ["modi", kModi]]) { + for (const s of SEITEN) { + const a = await seite(s, CREW, keks); + gezaehlt++; + if (a.code !== 200 && a.code !== 302 && a.code !== 404) { + seltsam++; + ok(false, `${s} für ${rolle}: ${a.code}`); + } + if (a.code !== 200) zu[rolle].push(s); + } + } + ok(gezaehlt === SEITEN.length * 3, + `${gezaehlt} Aufrufe: ${SEITEN.length} Seiten × 3 Rollen`); + ok(seltsam === 0, "keine einzige antwortet mit einem Fehler"); + /* UND DIE ZAHLEN GEHÖREN IN DIE BEDINGUNG, nicht nur in den Text: + Ein Durchgang, bei dem alles offen ist, hätte nichts geprüft. */ + ok(zu.admin.length > 8, `für DogFather sind ${zu.admin.length} Seiten zu`); + ok(zu.modi.length > zu.admin.length, + `für einen Modi mehr als für DogFather (${zu.modi.length})`); +} + +/* ------------------------------------------------------------------- */ +melde(""); +melde(`${geprueft} Pruefungen, ${fehler} Fehler`); +melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG"); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ } +process.exit(fehler ? 1 : 0); diff --git a/server/pruef-neue-seiten.mjs b/server/pruef-neue-seiten.mjs index 4422bc55..4fc3c5e5 100644 --- a/server/pruef-neue-seiten.mjs +++ b/server/pruef-neue-seiten.mjs @@ -475,8 +475,18 @@ for (const [name, pfad, keks] of [ `${wo}: die Seite schiebt nicht seitwärts (${r.breit.schiebt} px)` + (r.breit.raus.length ? " -- DRAUSSEN: " + r.breit.raus.join(", ") : "")); /* DIE ZAHL GEHÖRT IN DIE BEDINGUNG: Fände die Messung kein einziges - Textstück, wäre "nichts zu dunkel" trivial wahr. */ - ok(r.kontrast.gemessen > 15 && r.kontrast.schlecht.length === 0, + Textstück, wäre "nichts zu dunkel" trivial wahr. + + AUS 15 WURDE 0 (15.09.2026) -- und das ist kein Aufweichen. + Fünfzehn war eine feste Schwelle für "die Seite hat wirklich + gerendert", und genau diese Aufgabe erledigt zwei Zeilen weiter + oben schon die Messung der Zeichenzahl. Als die sechs Fragen von + entwicklung.html auf ihre eigene Seite umzogen, blieben bei + 390 px noch 14 Textstücke übrig -- alle lesbar, die Seite in + Ordnung, die Prüfung rot. Die Zahl hätte man auf 14 setzen + können; das wäre derselbe Fehler ein zweites Mal mit einer + anderen Zahl gewesen. */ + ok(r.kontrast.gemessen > 0 && r.kontrast.schlecht.length === 0, `${wo}: ${r.kontrast.gemessen} Textstücke, alle lesbar` + (r.kontrast.schlecht.length ? " -- ZU DUNKEL: " + r.kontrast.schlecht.slice(0, 3).join(" | ") : "")); /* Und die Frage davor: Konnte überhaupt gemessen werden? Text, der diff --git a/server/pruef-treff.mjs b/server/pruef-treff.mjs index c5eb6a2f..de2675d6 100644 --- a/server/pruef-treff.mjs +++ b/server/pruef-treff.mjs @@ -546,7 +546,28 @@ for (const [name, pfad] of [ if (a.code === 200) { try { const j = JSON.parse(a.text); - const felder = Object.values(j).filter(Array.isArray); + /* EINE WORTLISTE IST KEIN LECK (15.09.2026). + + Diese Pruefung fragte "enthaelt die Antwort IRGENDEIN gefuelltes + Feld" -- und wurde rot, als das Aufgabenbrett am 15.09. die drei + Aufwandsstufen mitzuliefern begann (klein/mittel/gross). Die sind + fuer jeden Menschen dieselben, sie sagen ueber niemanden etwas, + und sie kommen mit, damit im Browser keine zweite Liste gepflegt + werden muss. + + Ab da meldete die Pruefung bei JEDEM Lauf einen Befund, der keiner + war -- und eine Warnung, die immer kommt, wird ueberlesen. Genau + deshalb steht sie hier jetzt praeziser, statt lauter. + + WAS EINE WORTLISTE VON DATEN UNTERSCHEIDET: Ein Datensatz in + diesem Haus hat eine `id`. Ein Vokabular hat einen `schluessel` + und keine. Das ist keine Auslegung, sondern die Bauweise -- jede + Tabelle vergibt id, jede Wortliste in workspace-*.js nicht. Ein + echter Eintrag kann dadurch nicht versehentlich als Vokabular + durchgehen. */ + const istWortliste = (f) => f.length > 0 && f.every((x) => x + && typeof x === "object" && typeof x.schluessel === "string" && x.id === undefined); + const felder = Object.values(j).filter(Array.isArray).filter((f) => !istWortliste(f)); leer = felder.every((f) => f.length === 0); /* Die eigene Person in einer Personenliste ist kein Leck. */ if (!leer && pfad.endsWith("personen")) { diff --git a/server/workspace.js b/server/workspace.js index 4b5b934c..0633e7c9 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -3896,6 +3896,87 @@ function schrankenPfad(roh) { Eintrag in rechte.js oeffnet sich fuer keine Rolle, auch nicht fuer DogFather -- und pruef-rechtetafel meldet sie, statt dass man es beim Ausprobieren merkt. */ +/* ===================================================================== + AUF DER ADRESSE VON TEAM DOGI GIBT ES DIE AGENTURSEITEN NICHT + (15.09.2026) + + Filipe, mit dem Bildschirmfoto des Start-Checks auf crew.: + „das gibt es in der app nicht, dass ist nur auf der workspace app + aber nicht hier." + + --------------------------------------------------------------------- + ES WAR GROESSER ALS DIESE EINE SEITE + + Die Haustrennung von 10.09. entscheidet, WAS jemand sieht -- sie + filtert die Daten und die Kacheln. Sie entschied nie, welche SEITEN + es auf einer Adresse gibt. Die Rechtetafel wiederum kennt nur Rollen, + keine Adressen. Zwischen beidem lag das Loch. + + Gemessen am 15.09.2026, auf crew.: + + DogFather 12 Seiten ohne Kachel erreichbar, 10 davon Agentur + (Automationen, Content, Scouting, Reports, Zahlen, + Team, Calls, Creator-Profil, Dashboard, Start-Check) + rechte Hand 5, davon 3 Agentur + ein Modi 6, davon 3 Agentur -- auch der Start-Check + + Der Start-Check sagt dort „Es gibt noch keinen Creator", weil die + Haustrennung ihm alle Daten wegnimmt. Das ist genau die Sorte Seite, + die aussieht wie ein Fehler: Sie laedt, sie ist leer, und niemand + weiss, ob das Absicht ist oder etwas kaputt. + + --------------------------------------------------------------------- + DIE REGEL WIRD ABGELEITET, NICHT GEPFLEGT + + Welche Seiten zu dieser Adresse gehoeren, steht schon irgendwo: + in den KACHELN, die dieselbe Adresse dieser Person zeigt. Eine + zweite Liste daneben waere die, die beim naechsten Umbau + auseinanderlaeuft -- diesen Fehler hat das Projekt schon zweimal + bezahlt (die abgeschriebenen Spalten am 07. und 11.09.). + + Dazu eine kurze Liste von Seiten, die in jedem Haus dazugehoeren und + trotzdem keine Kachel haben. Sie altert SICHER: Eine neue + Agenturseite ist hier automatisch gesperrt (richtig), eine neue + Crew-Seite ohne Kachel faellt beim ersten Klick auf (sichtbar) -- + nicht still. + + UND SIE AENDERT KEINE RECHTE. Was jemand DARF, steht weiterhin + allein in rechte.js. Diese Regel beantwortet eine andere Frage: + Gibt es das hier ueberhaupt? Deshalb steht sie neben der + Rechtepruefung und nicht in ihr. + + NUR DIE SEITEN, NICHT DIE SCHNITTSTELLEN. Die filtern seit dem + 10.09. selbst ueber person.haus und brauchen das hier nicht -- + eine zweite Rechtelogik davor waere die, die irgendwann etwas + durchlaesst. Wer das aendert, aendert es dort. + ===================================================================== */ + +/** Seiten, die in JEDEM Haus dazugehoeren und keine Kachel haben. */ +const OHNE_KACHEL_UEBERALL = new Set([ + /* Die Startseite selbst -- sie ist das Ziel der Umleitung. */ + "/workspace/start.html", + /* Die Regeln des Treffs. Sie gehoeren der Community und dem Team. */ + "/workspace/treff-regeln.html", + /* Die Entwicklungskarte. Ein Modi hat dafuer keine Kachel, kommt + aber ueber „Deine Karte“ und ueber den Verweis von befinden.html + dorthin -- und beides ist seine eigene Sache. */ + "/workspace/entwicklung.html", +]); + +/** Gibt es diese Seite auf der Adresse, auf der die Person gerade steht? */ +export function gehoertAufDieseAdresse(person, pfad) { + /* Auf der Agenturadresse aendert sich nichts. Dort leben diese + Seiten -- die Frage stellt sich nur andersherum. */ + if (person?.haus !== "crew") return true; + if (OHNE_KACHEL_UEBERALL.has(pfad)) return true; + const ziele = new Set( + [...(bereicheFuer(person) || []), ...(zusatzBereicheFuer(person) || [])] + /* Ohne den Frageteil: Die Brettkacheln zeigen auf + bereich.html?b=treff -- gemeint ist die Seite, nicht das Brett. */ + .map((k) => "/workspace/" + String(k?.ziel || "").split("?")[0])); + return ziele.has(pfad); +} + workspaceRouter.use((req, res, next) => { const pfad = schrankenPfad(req.path); if (!pfad.startsWith("/workspace/") || !pfad.endsWith(".html")) return next(); @@ -3914,6 +3995,13 @@ workspaceRouter.use((req, res, next) => { if (!darfSeite(person, pfad)) { return res.redirect(302, "/workspace/start.html"); } + /* UND: GIBT ES DIESE SEITE HIER? Dieselbe Umleitung wie oben und + nicht ein 404 -- an dieser Schranke gibt es eine Antwort auf + „nein", nicht zwei. Zwei Verhalten nebeneinander waeren die + Einladung, aus dem Unterschied etwas abzulesen. */ + if (!gehoertAufDieseAdresse(person, pfad)) { + return res.redirect(302, "/workspace/start.html"); + } return next(); }); diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 4c8f35f0..ff056be9 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609151402", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609151417", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609151402", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609151417", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609151402", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609151417", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609151402", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609151417", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/js/entwicklung.js b/workspace/assets/js/entwicklung.js index e572476a..df9632f6 100644 --- a/workspace/assets/js/entwicklung.js +++ b/workspace/assets/js/entwicklung.js @@ -352,9 +352,17 @@ return; } if (!r.fertig) { - satz.textContent = `Noch nichts fertig. Eine Beobachtung erscheint hier erst, ` - + `wenn alle ${r.beurteiler} sie gesetzt haben – eine halbe Karte ist ` - + `schlimmer als keine.`; + /* EINE ZAHL IN EINEM SATZ MUSS SICH VORLESEN LASSEN. „wenn alle 1 + sie gesetzt haben" ist keine Auskunft, sondern eine Rechnung -- + und wer einen Satz liest, der falsch klingt, glaubt auch dem + Rest der Seite weniger. Derselbe Fall wie bei der Ampel und + beim Takt auf befinden.html. */ + satz.textContent = r.beurteiler === 1 + ? 'Noch nichts fertig. Eine Beobachtung erscheint hier, sobald sie gesetzt ist ' + + '– eine halbe Karte wäre schlimmer als keine.' + : `Noch nichts fertig. Eine Beobachtung erscheint hier erst, ` + + `wenn alle ${r.beurteiler} sie gesetzt haben – eine halbe Karte ist ` + + `schlimmer als keine.`; return; } diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index f51fed94..5b166fe5 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -12,19 +12,19 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - + + + + + + - +
@@ -282,10 +282,10 @@ - - - - - + + + + +