diff --git a/server/mess-teilen.mjs b/server/mess-teilen.mjs
new file mode 100644
index 00000000..fe0b3b3f
--- /dev/null
+++ b/server/mess-teilen.mjs
@@ -0,0 +1,198 @@
+/* =====================================================================
+ WAS PASSIERT BEIM TEILEN WIRKLICH? (05.10.2026)
+
+ Filipe: "ich lade video hoch und es erscheint immer noch nicht",
+ dazu auf Nachfrage: die Seite bleibt haengen, es kommt KEINE
+ Meldung -- und auf der Crew-Adresse ist das Brett voll (49 Videos).
+
+ Gemessen war vorher schon: Server unveraendert, Sitzung gueltig,
+ TikTok antwortet (auch auf Kurzlinks), und im Protokoll steht heute
+ kein einziges `video_eingelesen` -- die Anfrage erreicht den Server
+ also gar nicht. `los()` in teilen.js macht keine Netzanfrage; wenn
+ die Seite dort stehenbleibt, ist das Skript gestolpert.
+
+ Diese Messung spielt genau das nach: Teilen-Seite mit einem Link
+ aufrufen, Browsermeldungen mitschreiben, und nachsehen, ob die
+ Ladezeile verschwindet. Dann denselben Weg bis zum Absenden.
+
+ KEIN PRUEFLAUF, sondern eine Messung -- sie stellt fest, was ist,
+ und sagt nicht, was sein soll.
+ ===================================================================== */
+
+import { mkdtempSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+
+const ordner = mkdtempSync(join(tmpdir(), "ws-teilen-"));
+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, "mess-teilen");
+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(180_000, "mess-teilen");
+await new Promise((r) => setTimeout(r, 900));
+const BASIS = `http://127.0.0.1:${PORT}`;
+
+const { DatabaseSync } = await import("node:sqlite");
+const { scryptSync, randomBytes } = await import("node:crypto");
+await fetch(BASIS + "/workspace/api/anmelden", {
+ method: "POST", headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ rolle: "admin", code: "x" }),
+}).catch(() => {});
+for (let i = 0; i < 60; i++) {
+ try {
+ const p = new DatabaseSync(process.env.WORKSPACE_DB);
+ try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); }
+ } catch { await new Promise((r) => setTimeout(r, 250)); }
+}
+{
+ const d = new DatabaseSync(process.env.WORKSPACE_DB);
+ const salt = randomBytes(16).toString("hex");
+ const hash = scryptSync("CODE-DOGI-0001", 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, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
+ .run("Filipe", "admin", hash, salt, 32768, new Date().toISOString());
+ d.close();
+}
+
+const { chromium } = await import("playwright");
+const browser = await chromium.launch();
+const kontext = await browser.newContext({
+ viewport: { width: 390, height: 844 }, hasTouch: true, isMobile: true,
+});
+const seite = await kontext.newPage();
+
+const klagen = [];
+seite.on("console", (m) => { if (m.type() === "error") klagen.push("Konsole: " + m.text()); });
+seite.on("pageerror", (f) => klagen.push("ABSTURZ: " + String(f?.message || f)));
+seite.on("requestfailed", (r) => klagen.push("Anfrage gescheitert: " + r.url().replace(BASIS, "")));
+
+await seite.goto(BASIS + "/workspace/", { waitUntil: "domcontentloaded" });
+await seite.click('.rolle[data-rolle="admin"]');
+await seite.fill("#code", "CODE-DOGI-0001");
+await seite.click("button[type=submit]");
+await seite.waitForURL("**/start.html", { timeout: 20000 });
+
+/* ---- Genau so, wie die App beim Teilen aufruft ---------------------- */
+const LINK = "https://vm.tiktok.com/ZN8h3hbHr/";
+await seite.goto(`${BASIS}/workspace/teilen.html?url=${encodeURIComponent(LINK)}`,
+ { waitUntil: "domcontentloaded" });
+await seite.waitForTimeout(1500);
+
+const lage = await seite.evaluate(() => {
+ const z = (id) => document.getElementById(id);
+ return {
+ ladezeileWeg: z("ladezeile")?.hidden === true,
+ wahlDa: z("wahl")?.hidden === false,
+ knoepfeDa: z("knoepfe")?.hidden === false,
+ fundDa: z("fund")?.hidden === false,
+ linkText: z("url-text")?.textContent || "",
+ knopfText: z("uebernehmen")?.textContent || "",
+ fehlerText: z("fehler")?.textContent || "",
+ };
+});
+
+console.log("\n=== Die Teilen-Seite nach dem Aufruf ===");
+console.log(` Ladezeile verschwunden : ${lage.ladezeileWeg}`);
+console.log(` Auswahl sichtbar : ${lage.wahlDa}`);
+console.log(` Knopfreihe sichtbar : ${lage.knoepfeDa}`);
+console.log(` Link erkannt : ${lage.fundDa} "${lage.linkText}"`);
+console.log(` Knopf : "${lage.knopfText.trim()}"`);
+console.log(` Fehlerzeile : "${lage.fehlerText.trim()}"`);
+console.log(` Browsermeldungen : ${klagen.length ? klagen.join(" | ") : "keine"}`);
+
+/* ---- Und jetzt absenden -------------------------------------------- */
+if (lage.knoepfeDa) {
+ const antwort = seite.waitForResponse(
+ (r) => /\/api\/bereich\/[a-z]+\/video$/.test(r.url()), { timeout: 20000 }).catch(() => null);
+ await seite.click("#uebernehmen");
+ const a = await antwort;
+ await seite.waitForTimeout(1500);
+ const danach = await seite.evaluate(() => ({
+ fertig: document.getElementById("fertig")?.hidden === false,
+ satz: document.getElementById("fertig-satz")?.textContent || "",
+ fehler: document.getElementById("fehler")?.textContent || "",
+ knopf: document.getElementById("uebernehmen")?.textContent || "",
+ }));
+ console.log("\n=== Nach dem Absenden ===");
+ console.log(` Antwort vom Server : ${a ? a.status() : "KEINE -- es ging nichts hinaus"}`);
+ console.log(` Erfolg sichtbar : ${danach.fertig} "${danach.satz.trim()}"`);
+ console.log(` Fehlerzeile : "${danach.fehler.trim()}"`);
+ console.log(` Knopf steht auf : "${danach.knopf.trim()}"`);
+ console.log(` Browsermeldungen : ${klagen.length ? klagen.join(" | ") : "keine"}`);
+
+ const d = new DatabaseSync(process.env.WORKSPACE_DB);
+ const n = d.prepare("SELECT COUNT(*) AS n FROM eintraege WHERE bereich = 'highlight'").get().n;
+ d.close();
+ console.log(` Eintraege im Brett : ${n}`);
+} else {
+ console.log("\n Die Seite ist gar nicht erst fertig geworden -- kein Absenden moeglich.");
+}
+
+/* =====================================================================
+ GEGENPROBE: Was passiert, wenn der Aufbau WIRKLICH stolpert?
+
+ Ohne sie waere die Sicherung von heute eine Behauptung. Gepruefft
+ wird am haerteren Fall: Ein Element, das `los()` sofort anfasst,
+ wird aus der Seite entfernt, bevor das Skript laeuft. Vorher blieb
+ die Seite danach fuer immer auf "wird geladen" stehen -- genau
+ Filipes Bild.
+ ===================================================================== */
+{
+ const s2 = await kontext.newPage();
+ const klagen2 = [];
+ s2.on("pageerror", (f) => klagen2.push(String(f?.message || f)));
+ /* DIE ZERSTOERUNG MUSS SICHER ANKOMMEN (berichtigt 05.10.2026).
+
+ Der erste Versuch tauschte die Kennung im HTML unterwegs aus --
+ und kam nie an: Die Seite lud normal, der Browser meldete nichts,
+ und die Messung schloss daraus "die Sicherung greift nicht".
+ Eine Gegenprobe, die ihren eigenen Schaden nicht anrichtet,
+ misst gar nichts.
+
+ Jetzt wird `document.getElementById` ersetzt, bevor irgendein
+ Skript laeuft -- genau das, was `$` in teilen.js benutzt. Damit
+ wirft `los()` zuverlaessig an derselben Stelle wie bei einem
+ echten Fehler. */
+ await s2.addInitScript(() => {
+ const echt = document.getElementById.bind(document);
+ document.getElementById = (id) => {
+ if (id === "bretter") throw new Error("Probe: Element fehlt");
+ return echt(id);
+ };
+ });
+ await s2.goto(`${BASIS}/workspace/teilen.html?url=${encodeURIComponent(LINK)}`,
+ { waitUntil: "domcontentloaded" });
+ await s2.waitForTimeout(1200);
+ const kaputt = await s2.evaluate(() => {
+ const z = (id) => document.getElementById(id);
+ return {
+ ladezeileWeg: z("ladezeile")?.hidden === true,
+ meldung: (z("fehler")?.textContent || "").trim(),
+ eingabeDa: z("haendisch")?.hidden === false,
+ knopfDa: z("uebernehmen") !== null,
+ };
+ });
+ console.log("\n=== Gegenprobe: Seite absichtlich zerstoert ===");
+ console.log(` Ladezeile verschwunden : ${kaputt.ladezeileWeg}`);
+ console.log(` Meldung sichtbar : "${kaputt.meldung.slice(0, 96)}"`);
+ console.log(` Einfuegefeld offen : ${kaputt.eingabeDa}`);
+ console.log(` (Browser meldete : ${klagen2.length ? klagen2[0].slice(0, 60) : "nichts"})`);
+ console.log(kaputt.ladezeileWeg && kaputt.meldung && kaputt.eingabeDa
+ ? " -> Die Seite sagt, dass etwas kaputt ist, und bietet den Weg von Hand an."
+ : " -> SIE HAENGT IMMER NOCH. Die Sicherung greift nicht.");
+ await s2.close();
+}
+
+await kontext.close();
+await browser.close();
+try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
+process.exit(0);
diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html
index f8346fe3..f50d832c 100644
--- a/workspace/anruf-probe.html
+++ b/workspace/anruf-probe.html
@@ -16,8 +16,8 @@
verdeckt. Gefunden hat es pruef-struktur.
Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. -->
-
-
+
+
-
+