Files
dogfather-universe/workspace/leistung.html
T
DogFatherGitandClaude Opus 5 bcc1c70f4a Spicy Media legt Manager UND Scouts an -- eine Liste statt vier
Filipe, mit Bildschirmfoto der Zugaenge-Seite: "die spicy rolle soll
auch manager und scouts hinzufuegen koennen."

DER MANAGER-KNOPF FEHLTE NICHT AUS RECHTEGRUENDEN. Serverseitig war die
Tuer /workspace/api/manager-anlegen fuer Spicy Media die ganze Zeit
offen. Es gab nur nichts zum Draufdruecken -- wegen ZWEIER Listen in
derselben Funktion, drei Zeilen auseinander (personen.js):

  const darf = ... spicy ? ['manager', 'creator'] : ['creator'];
  ...
  if ((r.wert === 'admin' || r.wert === 'manager') && ich.rolle !== 'admin') continue;

Die erste erlaubt den Manager, die zweite nimmt ihn wieder weg. Uebrig
blieb ein einziger Knopf: Creator. Nichts war kaputt, nichts wurde rot,
es fehlte einfach -- die Sorte Fehler, die nur jemandem auffaellt, der
davorsitzt.

FUER SCOUTS GAB ES UEBERHAUPT KEINE TUER. Nur DogFather konnte welche
anlegen. Und die Wegwahl in der Oberflaeche war eine Kette mit
Auffangbecken (`rolle === 'manager' ? ... : creator-anlegen`): Ein Scout
waere im else gelandet, und creator-anlegen legt IMMER einen Creator an.
Der Knopf haette Erfolg gemeldet und das Falsche getan.

DIE ANTWORT STEHT JETZT AN EINER STELLE. `darfAnlegen` in workspace.js
sagt, wer wen anlegen darf. Daraus lesen:

  - die beiden Team-Tueren (Manager, Scout)
  - die allgemeine Verwaltungs-Tuer von DogFather
  - die Oberflaeche, ueber `darf_anlegen` in /workspace/api/ich

Die Oberflaeche hat damit gar keine eigene Liste mehr und kann deshalb
auch nicht mehr abweichen -- weder zu streng noch zu grosszuegig.

ZWEI TUEREN, NICHT EINE MIT EINEM ROLLENFELD. Der Absatz an der
Manager-Tuer raet davon ab, und der Rat gilt: Eine Tuer, die NICHTS
anderes kann, als eine bestimmte Rolle anzulegen, ist sicherer als eine,
die vorher nachfragt. `teamTuer(rolle)` baut beide aus demselben Text --
die Rolle wird beim Einhaengen festgelegt und kommt nie aus dem Aufruf.
Geprueft: ein mitgeschicktes "rolle: admin" bleibt wirkungslos.

GEPRUEFT (pruef-creator-anlegen, 33 -> 49 Pruefungen)
  - Spicy Media legt Manager an       -> 201, Rolle stimmt
  - Spicy Media legt Scout an         -> 201, Rolle stimmt
  - "rolle: admin" mitgeschickt       -> wirkungslos, es wird ein Scout
  - ein Manager durch die Scout-Tuer  -> 404
  - ein Scout durch die Scout-Tuer    -> 404
  - Personenliste lesen               -> 200 (die eine gewollte Ausnahme)
  - darueber anlegen                  -> 404, und es entsteht niemand
  - /api/ich nennt Spicy: manager, scout, creator -- und keinen DogFather
  - ein Manager bekommt genau eine Rolle genannt, ein Scout keine
  - die Knoepfe auf der Seite stimmen mit alldem ueberein
  - DogFather sieht unveraendert alle -- gemessen, nicht geglaubt

Der erste Anlauf der Pruefung behauptete, Spicy Media komme gar nicht an
/workspace/api/verwaltung. Falsch, und sie wurde zu Recht rot: nurAdmin
laesst genau einen Fall durch, das LESEN der Personenliste. Diese
Trennung ist jetzt festgenagelt.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-10 21:56:45 +02:00

227 lines
9.3 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Zahlen · Spicy & Dogi</title>
<meta name="robots" content="noindex, nofollow" />
<link rel="manifest" href="/workspace/app.webmanifest" />
<!-- Die Farbe der Fensterleiste, wenn der Workspace als App laeuft.
Stand auf #0674b9 (kraeftiges Blau) und stach damit als einziger
heller Balken ueber einer durchweg dunklen Seite. Jetzt derselbe
Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite
eine Flaeche sind statt zweier. -->
<meta name="theme-color" content="#06090f" />
<link rel="icon" type="image/png" href="/assets/img/app-symbole/workspace-32.png?v=202609102156" />
<link rel="apple-touch-icon" href="/assets/img/app-symbole/workspace-180.png?v=202609102156" />
<link rel="stylesheet" href="assets/css/gate.css?v=202609102156" />
<link rel="stylesheet" href="assets/css/start.css?v=202609102156" />
<link rel="stylesheet" href="assets/css/aufgaben.css?v=202609102156" />
<link rel="stylesheet" href="assets/css/leistung.css?v=202609102156" />
<link rel="stylesheet" href="assets/css/module.css?v=202609102156" />
</head>
<body class="start">
<div class="schleier" aria-hidden="true"></div>
<header class="kopfleiste">
<button type="button" class="zurueck-knopf" id="zurueck" hidden>
<svg class="zurueck-knopf__pfeil" viewBox="0 0 24 24" aria-hidden="true">
<path d="M14.5 5.5 8 12l6.5 6.5" />
</svg>
<span class="zurueck-knopf__text">Zurück</span>
</button>
<p class="marke"><span class="marke__text"><a class="zurueck" href="start.html">Creator&nbsp;Workspace</a> · Zahlen</span></p>
<div class="kopfleiste__rechts">
<span class="wer" id="wer">…</span>
<button type="button" class="abmelden" id="abmelden">Abmelden</button>
</div>
</header>
<main class="inhalt inhalt--breit">
<section class="kopf-zeile">
<div>
<p class="marke">Zahlen</p>
<h1 class="titel">Was wirklich passiert ist.</h1>
</div>
<div class="leistung__wahl" id="creator-wahl" hidden>
<label class="feld-schild" for="creator">Creator</label>
<select id="creator"></select>
</div>
</section>
<p class="fehler" id="fehler" role="alert" aria-live="polite"></p>
<!-- DIE WOCHE IN ZAHLEN.
Jede Karte trägt die drei Dinge, die eine Kennzahl braucht
(Recherche 31.08./06.09.2026): den WERT, den VERGLEICH zur
Vorwoche und den VERLAUF als kleine Linie. Eine Zahl ohne diese
drei ist eine Eitelkeitszahl -- sie sieht nach Auskunft aus, und
man kann nichts damit entscheiden. -->
<section class="leistung-woche" id="woche" hidden>
<div class="klapp-kopf">
<p class="feldschild">Diese Woche</p>
<p class="leistung__erfassung" id="erfassung"></p>
</div>
<div class="zahlkarten" id="zahlkarten"></div>
</section>
<!-- Die Ziele als Fortschritt, nicht als Satz. -->
<section class="leistung-ziele" id="ziele-block" hidden>
<div class="klapp-kopf">
<p class="feldschild">Ziele</p>
<button type="button" class="knopf-still" id="ziele-aendern" hidden>Ziele setzen</button>
</div>
<div class="zielbalken" id="zielbalken"></div>
</section>
<!-- Die Tageszeilen. Tastaturfreundlich: Eine Woche soll in unter
einer Minute eingetragen sein. -->
<section class="leistung-tage" id="tage-block" hidden>
<div class="klapp-kopf">
<p class="feldschild">Die letzten Tage</p>
<!-- Zwei Knöpfe rechts: erst der Schalter (er ordnet die Liste),
dann die Hauptaktion. Der Schalter trägt bewusst die stille
Ausführung -- er verändert nichts an den Daten. -->
<div class="klapp-kopf__knoepfe">
<button type="button" class="knopf-still" id="tage-schalter"
aria-expanded="false" aria-controls="tagetabelle" hidden>
<span id="tage-schalter-text">Alle zeigen</span>
<svg class="klapp-pfeil" viewBox="0 0 24 24" aria-hidden="true">
<path d="m6 9.5 6 6 6-6" />
</svg>
</button>
<button type="button" class="knopf knopf--klein" id="import-oeffnen">Aus Datei einlesen</button>
</div>
</div>
<div class="tagetabelle" id="tagetabelle"></div>
</section>
<p class="fuss">
Die Zahlen kommen aus dem offiziellen Export von TikTok LIVE Backstage –
entweder von Hand eingetragen oder als Datei eingelesen. Es wird nichts
automatisch abgerufen: Dafür gibt es keine offene Schnittstelle.
</p>
</main>
<!-- Einen Tag eintragen -->
<dialog class="dialog" id="tag-dialog">
<form method="dialog" id="tag-form" class="neu neu--blank">
<h2 class="dialog__titel" id="tag-titel">Tag eintragen</h2>
<p class="dialog__unter" id="tag-unter"></p>
<div class="neu__raster">
<div>
<label class="feld-schild" for="f-diamanten">Diamanten</label>
<input id="f-diamanten" type="number" min="0" inputmode="numeric" />
</div>
<div>
<label class="feld-schild" for="f-dauer">LIVE-Dauer <span class="leise">Minuten</span></label>
<input id="f-dauer" type="number" min="0" inputmode="numeric" />
</div>
<div>
<label class="feld-schild" for="f-verweildauer">Ø Verweildauer <span class="leise">Sekunden</span></label>
<input id="f-verweildauer" type="number" min="0" inputmode="numeric" />
</div>
<div>
<label class="feld-schild" for="f-zavg">Ø Zuschauer</label>
<input id="f-zavg" type="number" min="0" inputmode="numeric" />
</div>
<div>
<label class="feld-schild" for="f-zmax">Spitze</label>
<input id="f-zmax" type="number" min="0" inputmode="numeric" />
</div>
<div>
<label class="feld-schild" for="f-schenker">Schenker</label>
<input id="f-schenker" type="number" min="0" inputmode="numeric" />
</div>
<div>
<label class="feld-schild" for="f-follower">Neue Follower</label>
<input id="f-follower" type="number" min="0" inputmode="numeric" />
</div>
</div>
<!-- Der Kontext, ohne den Zahlen in die Irre führen. -->
<div class="neu__zeile">
<label class="feld-schild" for="f-notiz">
Was war an dem Tag besonders? <span class="leise">(optional)</span>
</label>
<input id="f-notiz" maxlength="500" placeholder="z. B. PK gegen Mia, krank, Technik ausgefallen" />
</div>
<p class="fehler" id="tag-fehler" role="alert" aria-live="polite"></p>
<div class="dialog__knoepfe">
<button type="submit" class="knopf knopf--klein" id="tag-speichern">Speichern</button>
<button type="button" class="abmelden" id="tag-zu">Abbrechen</button>
<button type="button" class="abmelden abmelden--warnung" id="tag-loeschen" hidden>Zeile löschen</button>
</div>
</form>
</dialog>
<!-- Ziele setzen -->
<dialog class="dialog" id="ziele-dialog">
<form method="dialog" id="ziele-form" class="neu neu--blank">
<h2 class="dialog__titel">Ziele je Woche</h2>
<p class="dialog__unter">
Aus „mehr Reichweite" wird eine prüfbare Größe. Leer lassen heißt: kein Ziel.
</p>
<div class="neu__raster">
<div>
<label class="feld-schild" for="z-diamanten">Diamanten pro Woche</label>
<input id="z-diamanten" type="number" min="0" inputmode="numeric" />
</div>
<div>
<label class="feld-schild" for="z-tage">LIVE-Tage pro Woche</label>
<input id="z-tage" type="number" min="0" max="7" inputmode="numeric" />
</div>
<div>
<label class="feld-schild" for="z-verweildauer">Ø Verweildauer <span class="leise">Sekunden</span></label>
<input id="z-verweildauer" type="number" min="0" inputmode="numeric" />
</div>
</div>
<p class="fehler" id="ziele-fehler" role="alert" aria-live="polite"></p>
<div class="dialog__knoepfe">
<button type="submit" class="knopf knopf--klein" id="ziele-speichern">Speichern</button>
<button type="button" class="abmelden" id="ziele-zu">Abbrechen</button>
</div>
</form>
</dialog>
<!-- Aus Datei einlesen. ERST VORSCHAU, DANN ÜBERNEHMEN -->
<dialog class="dialog dialog--breit" id="import-dialog">
<form method="dialog" id="import-form" class="neu neu--blank">
<h2 class="dialog__titel">Aus dem Export einlesen</h2>
<p class="dialog__unter">
Die CSV-Datei aus TikTok LIVE Backstage. Erst siehst du, was passieren
würde – geschrieben wird erst, wenn du bestätigst. Dieselbe Datei
zweimal einzulesen richtet keinen Schaden an.
</p>
<div class="neu__zeile">
<label class="feld-schild" for="datei">Datei wählen</label>
<input id="datei" type="file" accept=".csv,text/csv,text/plain" />
</div>
<div class="import-vorschau" id="vorschau" hidden></div>
<p class="fehler" id="import-fehler" role="alert" aria-live="polite"></p>
<div class="dialog__knoepfe">
<button type="submit" class="knopf knopf--klein" id="import-los" disabled>Übernehmen</button>
<button type="button" class="abmelden" id="import-zu">Abbrechen</button>
</div>
</form>
</dialog>
<script src="assets/js/wahl.js?v=202609102156"></script>
<script src="assets/js/bereiche.js?v=202609102156"></script>
<script src="assets/js/kopf.js?v=202609102156"></script>
<script src="assets/js/glocke.js?v=202609102156"></script>
<script src="assets/js/leistung.js?v=202609102156"></script>
</body>
</html>