Kampagnen: auswaehlen, wo die Kachel hingehoert -- TikTok oder Agentur

Filipe, mit einem Bild des Einlesekastens: "das sind die von der
agentur selbst. die krieg ich so, perfektioniere das und auch das ich
auswaehlen kann wo es hingehoert, zu tiktok oder der agentur." Dazu
sein echter Link: https://vm.tiktok.com/ZSbACwAnK/

ZWEI FRAGEN, DIE BISHER EINE WAREN. "Woher kam der Link" und "wem
gehoert das Event" wurden beide aus `kampagne_id` beantwortet: Wer
ueber eine Einladung eingelesen wurde, stand links. Die Agentur
schickt ihre eigenen Events aber ebenfalls als TikTok-Link -- damit
war die Zuordnung zwangslaeufig falsch.

Neue Spalte `kampagne_seite` mit DREI Zustaenden, und NULL ist einer
davon: niemand hat gewaehlt -> wie bisher nach der Herkunft. Alle
bestehenden Kacheln behalten dadurch genau ihren Platz, ohne dass
etwas umgestellt werden musste. Gewaehlt wird im Einlesekasten
(Automatisch / TikTok / Agentur, "Automatisch" vorbelegt) und
nachtraeglich an der Kachel ("zur Agentur" / "zu TikTok"). Nur die
Leitung; bei einem Creator wird das Feld stillschweigend entfernt.

GEMESSEN AN SEINEM ECHTEN LINK, mit Gegenprobe:
  vm.tiktok.com/ZSbACwAnK/
    -> www.tiktok.com/tcn/activity?activity_id=7691301069758087175
    -> 233 KB reine Huelle, kein __MODERN_ROUTER_DATA__
  dieselbe Nummer auf dem Weg der oeffentlichen Kampagnenseite
    -> 42 KB, ebenfalls ohne Datenpaket
  eine echte Kampagne auf demselben Weg
    -> 967 KB MIT Datenpaket
Die Kampagne existiert also nicht als oeffentliche Seite: Das Creator
Network baut seine Seiten erst im Browser auf, angemeldet. Von aussen
kann das niemand lesen -- auch kein anderes Programm.

Daraus zwei Dinge:
  - `nummerAusAdresse` liest jetzt BEIDE Schreibweisen (`activityId`
    auf der Kampagnenseite, `activity_id` im Creator Network).
  - `istCreatorNetwork` erkennt solche Links VOR dem Abruf. Statt 502
    "TikTok hat nicht geantwortet" (eine Ursache, die es nicht gibt)
    kommt 422 mit dem, was wirklich los ist -- und mit dem Weg
    daneben: das Event von Hand anlegen, Link in die Beschreibung.

BERICHTIGT: Der PATCH-Weg stand als '/workspace/api/bereich/agentur/'
+ e.id im Browser und war fuer pruef-struktur nicht mehr zuzuordnen
-- jetzt `${bereich}` wie bei den Nachbarn, was nebenbei den zweiten
Ort mit "agentur" im Text wegnimmt. Und "Automatisch" hatte keinen
eigenen Ton und fiel auf `--akzent` zurueck: auf dem Agenturbrett
genau das Gelb der rechten Spalte, die vorbelegte Moeglichkeit sah
also aus wie "Agentur".

GEPRUEFT: 169 (Route, +22), 170 (Kachel, +12), 67 (Agentur), 67
Messungen (Bild), 107 (Haus-Trennung, unveraendert), dazu struktur,
css-klassen, zeichen, deutsche-texte, formulare, tippziele. Drei
Gegenproben: Auswahl verwerfen, Creator-Network durchlassen, Kachel
wieder nur nach Herkunft -- alle drei werden bemerkt, alle Dateien
byte-gleich wiederhergestellt.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-07 23:46:41 +02:00
co-authored by Claude Opus 5
parent cadab24a5f
commit 2764aea61c
54 changed files with 1333 additions and 702 deletions
+162 -6
View File
@@ -104,13 +104,13 @@ const tag = (v) => tagLokal(v);
Missionsgruppe. Ohne ihn waeren diese drei Dinge hier gar
nicht pruefbar -- und ein Feld, das keine Pruefung ansieht,
ist ein Feld, das beim naechsten Umbau still ausfaellt. */
function evt(titel, von, bis, auf, reg, kampagneId = null, daten = null) {
function evt(titel, von, bis, auf, reg, kampagneId = null, daten = null, seite = null) {
d.prepare("INSERT INTO eintraege (titel, text, bereich, art, status, datum, event_ende,"
+ " event_aufgaben, event_regeln, dringlichkeit, erstellt, erstellt_von, kampagne_id,"
+ " kampagne_daten)"
+ " VALUES (?,?,'agentur','kampagne','offen',?,?,?,?,'mittel',?,?,?,?)")
+ " kampagne_daten, kampagne_seite)"
+ " VALUES (?,?,'agentur','kampagne','offen',?,?,?,?,'mittel',?,?,?,?,?)")
.run(titel, "Beschreibung zum Test.", von, bis, auf, reg, jetzt, idDogi, kampagneId,
daten === null ? null : JSON.stringify(daten));
daten === null ? null : JSON.stringify(daten), seite);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
}
/* Die drei Zustaende, die eventStand kennt -- jeder einzeln, nicht als
@@ -167,6 +167,22 @@ const struktText = [
"TEILNAHME",
"Länder: DE, CH, AT, LI, LU",
].join("\n");
/* ==== VON TIKTOK, ABER NICHT VON TIKTOK (07.10.2026) ========
Filipe: „das sind die von der agentur selbst. die krieg ich so,
... ich will auswaehlen koennen wo es hingehoert, zu tiktok oder
der agentur."
DAS IST DER FALL, UM DEN ES GEHT: Die Agentur schickt ihre
eigenen Events ebenfalls als TikTok-Link. Diese Kachel hat
also eine `kampagne_id` -- und gehoert trotzdem nach RECHTS.
Ohne sie liesse sich „die Wahl gewinnt gegen die Herkunft" gar
nicht messen: Bei einer Kachel ohne `kampagne_id` stuende
rechts ja ohnehin das Richtige. */
const agenturEvent = evt("Agentur-Aktion über TikTok", tag(-1), tag(14),
"Eigene Prämie einlösen", "· Nur für das eigene Team",
"7600000000000000009", null, "agentur");
const strukt = evt("Glow Up Struktur", tag(-2), tag(20),
"Tägliche Mission\nWöchentliche Mission", struktText,
"7690927903777262344", {
@@ -613,8 +629,11 @@ async function alsRolle(rolle, code, breite = 1280) {
ZUGEKLAPPTE GRUPPEN ZAEHLEN MIT: „Vorbei" steht von sich aus
zu, aber `hidden` entfernt ein Element nicht aus dem Baum.
Waere das anders, muesste hier erst aufgeklappt werden. */
/* FUENF SEIT DEM 07.10.2026 (zweite Runde): dazu kam
„Agentur-Aktion über TikTok" -- eine Kachel MIT kampagne_id,
die trotzdem rechts gehoert. */
const karten = await seite.$$('.eintrag-karte[data-eventkarte="ja"]');
ok(karten.length === 4, `4 Eventkarten (${karten.length}) -- die Schulung ist keine`);
ok(karten.length === 5, `5 Eventkarten (${karten.length}) -- die Schulung ist keine`);
/* GEGENPROBE: Die Schulung bleibt eine gewoehnliche Karte. Ohne sie
waere "nur Events sind umgebaut" unbelegt. */
@@ -1305,7 +1324,7 @@ melde("\n=== 4b. Die Gliederung auf dem Bildschirm ===");
durch. Ohne diese Zeile waere „es gibt Gruppen" vereinbar damit,
dass ein Event in keiner davon auftaucht. */
const summe = alleGruppen.reduce((s, g) => s + Number(g.zahl), 0);
ok(summe === 4, `alle 4 Events stehen in genau einer Gruppe (Summe ${summe})`);
ok(summe === 5, `alle 5 Events stehen in genau einer Gruppe (Summe ${summe})`);
/* GEGENPROBE: Links und rechts teilen sich den Zustand NICHT.
Dieser Fehler war bei den Zeitstufen schon einmal da.
@@ -1370,6 +1389,143 @@ melde("\n=== 4b. Die Gliederung auf dem Bildschirm ===");
await ctx.close();
}
/* =======================================================================
4c. WOHIN GEHÖRT DIE KACHEL? (07.10.2026)
Die Herkunft beantwortet „woher kam der Link“, die Frage auf dem
Brett ist aber „wem gehört das Event“. Gemessen wird deshalb
genau der Fall, in dem beide auseinandergehen.
======================================================================= */
melde("\n=== 4c. Die Seitenwahl ===");
{
const { ctx, seite } = await alsRolle("admin", "CODE-DOGI-0001", 1400);
const lage = await seite.evaluate(() => {
const seiten = [...document.querySelectorAll(".kv-seite")].map((s) => ({
seite: s.dataset.seite,
titel: [...s.querySelectorAll(".eintrag-titel")].map((x) => x.textContent.trim()),
}));
return { seiten };
});
melde(" -- Lage: " + JSON.stringify(lage));
const links = lage.seiten.find((s) => s.seite === "tiktok")?.titel || [];
const rechts = lage.seiten.find((s) => s.seite === "agentur")?.titel || [];
/* DIE EINE ZEILE, UM DIE ES GEHT. */
ok(rechts.includes("Agentur-Aktion über TikTok"),
`eine Kachel MIT kampagne_id steht rechts, weil sie dorthin gewählt wurde `
+ `(rechts: ${JSON.stringify(rechts)})`);
ok(!links.includes("Agentur-Aktion über TikTok"),
" und nicht zusätzlich links");
/* GEGENPROBE: Eine Kachel OHNE Wahl steht weiterhin nach ihrer
Herkunft. Ohne sie wäre „die Wahl gewinnt“ auch dann grün,
wenn gar nichts mehr links landet. */
ok(links.includes("Oktober Event") && links.includes("Glow Up Struktur"),
`ohne Wahl entscheidet weiterhin die Herkunft (links: ${JSON.stringify(links)})`);
/* ---- Die Auswahl im Kasten -------------------------------- */
const wahl = await seite.evaluate(() => {
const f = document.querySelector(".kampagneholen__wahl");
if (!f) return null;
const k = [...f.querySelectorAll('input[name="kampagne-seite"]')];
return {
werte: k.map((x) => x.value),
gewaehlt: k.find((x) => x.checked)?.value ?? null,
woerter: [...f.querySelectorAll("span")].map((x) => x.textContent.trim()),
sichtbar: f.getBoundingClientRect().height > 0,
};
});
melde(" -- Auswahl: " + JSON.stringify(wahl));
ok(wahl && wahl.werte.length === 3, `drei Möglichkeiten (${wahl?.werte.length})`);
ok(wahl?.werte.includes("tiktok") && wahl?.werte.includes("agentur"),
`TikTok und Agentur stehen zur Wahl: ${JSON.stringify(wahl?.werte)}`);
/* „Automatisch“ IST VORBELEGT. Eine Auswahl, die man jedes Mal
treffen muss, ist ein Hindernis -- und sie wäre eine
Verhaltensänderung für alles, was bisher funktioniert hat. */
ok(wahl?.gewaehlt === "", `„Automatisch“ ist vorbelegt (${JSON.stringify(wahl?.gewaehlt)})`);
ok(wahl?.sichtbar === true, "und der Kasten ist wirklich zu sehen");
/* ---- Der Knopf an der Kachel ------------------------------ */
const knopf = await seite.evaluate(() => {
const k = [...document.querySelectorAll('.eintrag-karte[data-eventkarte="ja"]')]
.find((x) => x.textContent.includes("Oktober Event"));
const b = [...(k?.querySelectorAll(".schritt--seite") || [])];
return { anzahl: b.length, wort: b[0]?.textContent.trim() || null };
});
/* „Oktober Event“ steht links -- der Knopf muss also nach rechts
führen und das auch sagen. Ein Knopf, der nur „wechseln“ heißt,
ließe offen, wohin. */
ok(knopf.anzahl === 1 && knopf.wort === "zur Agentur",
`der Knopf nennt sein Ziel: ${JSON.stringify(knopf.wort)}`);
await seite.evaluate(() => {
const k = [...document.querySelectorAll('.eintrag-karte[data-eventkarte="ja"]')]
.find((x) => x.textContent.includes("Oktober Event"));
k.querySelector(".schritt--seite").click();
});
await seite.waitForTimeout(1200);
const danach = await seite.evaluate(() => {
const s = [...document.querySelectorAll(".kv-seite")].map((x) => ({
seite: x.dataset.seite,
titel: [...x.querySelectorAll(".eintrag-titel")].map((y) => y.textContent.trim()),
}));
const k = [...document.querySelectorAll('.eintrag-karte[data-eventkarte="ja"]')]
.find((x) => x.textContent.includes("Oktober Event"));
return { s, wort: k?.querySelector(".schritt--seite")?.textContent.trim() || null };
});
melde(" -- nach dem Druck: " + JSON.stringify(danach));
const rechtsDanach = danach.s.find((x) => x.seite === "agentur")?.titel || [];
const linksDanach = danach.s.find((x) => x.seite === "tiktok")?.titel || [];
ok(rechtsDanach.includes("Oktober Event") && !linksDanach.includes("Oktober Event"),
`ein Druck verschiebt die Kachel wirklich `
+ `(links ${linksDanach.length}, rechts ${rechtsDanach.length})`);
/* UND DER KNOPF ZEIGT JETZT ZURÜCK. Ein Weg ohne Rückweg wäre
beim ersten Versehen ein Dauerzustand. */
ok(danach.wort === "zu TikTok",
`und führt jetzt zurück: ${JSON.stringify(danach.wort)}`);
await ctx.close();
}
/* ---- Und ein Creator sieht von alledem nichts ---------------- */
{
const { ctx, seite } = await alsRolle("creator", "CODE-CREA-0001", 1400);
/* GEMESSEN WIRD, WAS MAN SIEHT -- nicht, was im Baum steht
(berichtigt 07.10.2026). Hier stand `querySelectorAll(...).length`,
und das zählte 1: Der ganze Kasten `#kampagneholen` ist für einen
Creator `hidden`, aber `hidden` entfernt nichts aus dem Baum.
Die Prüfung meldete einen Schaden, den es nicht gab.
Es ist derselbe Fehlgriff wie zwei Stunden vorher bei den
zugeklappten Gruppen -- und deshalb wird hier GENAU dieselbe
Frage gestellt: Hat das Element ein Rechteck? */
const nix = await seite.evaluate(() => {
const sichtbar = (w) => !!w && w.getBoundingClientRect().height > 0;
const kasten = document.querySelector(".kampagneholen__wahl");
return {
kastenDa: !!kasten,
kastenSichtbar: sichtbar(kasten),
abschnittZu: document.getElementById("kampagneholen")?.hidden === true,
knoepfe: [...document.querySelectorAll(".schritt--seite")].filter(sichtbar).length,
karten: document.querySelectorAll('.eintrag-karte[data-eventkarte="ja"]').length,
};
});
melde(" -- Creator: " + JSON.stringify(nix));
/* DIE KARTENZAHL GEHÖRT IN DIE BEDINGUNG: Ohne sie wäre „ein
Creator sieht die Knöpfe nicht“ auch auf einer leeren Seite
grün (CLAUDE.md, 01.09.2026). */
ok(nix.karten > 0 && !nix.kastenSichtbar && nix.knoepfe === 0,
`ein Creator sieht ${nix.karten} Kacheln, aber keine Seitenwahl `
+ `(Kasten sichtbar: ${nix.kastenSichtbar}, ${nix.knoepfe} Knöpfe)`);
/* UND ZWAR, WEIL DER GANZE ABSCHNITT ZU IST -- nicht, weil die
Auswahl zufällig keine Höhe hat. Eine Begründung, die man nicht
mitprüft, kann beim nächsten Umbau wegfallen, ohne dass es
auffällt. */
ok(nix.abschnittZu === true,
`und der ganze Einlese-Kasten ist zu (hidden=${nix.abschnittZu})`);
await ctx.close();
}
melde("\n=== 5. Abhaken im Browser, mit Neuladen ===");
{
const { ctx, seite } = await alsRolle("creator", "CODE-CREA-0001", 1280);