Regelwerk, Agenten, Skills und Werkzeuge veröffentlichen

Erster Stand des öffentlichen Ausschnitts aus dem Vault. Die .gitignore
arbeitet als Positivliste: ausgeschlossen ist alles, eingeschlossen nur,
was ausdrücklich wieder aufgenommen wird. Notizen, Projekte, Finanzen,
das Sitzungsgedächtnis und settings.local.json bleiben damit außen vor.

Der Kundenname in AI/Regelwerk/seo.md ist durch eine neutrale
Bezeichnung ersetzt; die Regeln selbst sind unverändert.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Pascal MichelandClaude Opus 5 committed 2026-09-17 22:38:15 +02:00
commit 2c9a4475fc
97 files changed
+14627

No files matched your search

@@ -0,0 +1,128 @@
"""Browser-Lauf zu Teil 3b mit Playwright (Windows-Python), gegen ein laufendes Backend.
Koordinator-Pruefskript, nicht fuers Repo. Aufruf:
python -X utf8 browserlauf.py <basis-url> <ausgabeordner>
Prueft je Breite: kein waagerechter Scroll, keine Konsolenfehler, Ansicht "Laeufe" zeigt Zeilen,
Ansicht "Markt" zeichnet (Canvas nicht leer). Schreibt Screenshots und eine ergebnis.json.
Was es nicht sieht: ob die Zahlen stimmen (dafuer pruef_teil3b.py) und wie es aussieht (dafuer
die Screenshots, die der Koordinator selbst ansieht).
"""
import json
import sys
from pathlib import Path
from playwright.sync_api import TimeoutError as PlaywrightTimeout
from playwright.sync_api import sync_playwright
BREITEN = [375, 768, 1024, 1440]
# Je ein Minuten- und ein Tagesdatensatz; die Namen stehen so im Katalog (gemessen 2026-09-17).
MARKT_DATENSAETZE = [
("minuten", "AAPL.XNAS-1-MINUTE-LAST-EXTERNAL"),
("tage", "MSFT.XNAS-1-DAY-LAST-EXTERNAL"),
]
# aria-label der Knoepfe aus Laeufe.tsx: `Lauf ${quelle}/${datei}`.
LAEUFE_EINZEL = [
("tage", "Lauf signal/tage-teil9-20260917-030800.jsonl"),
("kampagne", "Lauf laeufe/aapl-minute-ppo-v20260914-132555.jsonl"),
]
CANVAS_PROBE = """
() => {
const flaechen = Array.from(document.querySelectorAll('canvas'));
let groesste = null;
for (const c of flaechen) {
if (!groesste || c.width * c.height > groesste.width * groesste.height) groesste = c;
}
if (!groesste || groesste.width === 0 || groesste.height === 0) return {anzahl: flaechen.length, farben: 0};
const kontext = groesste.getContext('2d');
const daten = kontext.getImageData(0, 0, groesste.width, groesste.height).data;
const farben = new Set();
const schritt = Math.max(4, Math.floor(daten.length / 4 / 20000) * 4);
for (let i = 0; i < daten.length; i += schritt) {
farben.add(`${daten[i]},${daten[i+1]},${daten[i+2]},${daten[i+3]}`);
}
return {anzahl: flaechen.length, breite: groesste.width, hoehe: groesste.height, farben: farben.size};
}
"""
SCROLL_PROBE = "() => ({scroll: document.documentElement.scrollWidth, sicht: document.documentElement.clientWidth})"
def main() -> int:
basis = sys.argv[1].rstrip("/")
ziel = Path(sys.argv[2])
ziel.mkdir(parents=True, exist_ok=True)
ergebnis: dict = {"basis": basis, "breiten": {}, "befunde": []}
with sync_playwright() as spiel:
browser = spiel.chromium.launch()
for breite in BREITEN:
seite = browser.new_page(viewport={"width": breite, "height": 900})
meldungen: list[str] = []
seite.on("console", lambda m, liste=meldungen: liste.append(f"{m.type}: {m.text}") if m.type in ("error", "warning") else None)
seite.on("pageerror", lambda f, liste=meldungen: liste.append(f"pageerror: {f}"))
seite.goto(basis + "/", wait_until="networkidle")
eintrag: dict = {}
def messe(name: str) -> None:
masse = seite.evaluate(SCROLL_PROBE)
eintrag[f"scroll_{name}"] = masse
if masse["scroll"] > masse["sicht"] + 1:
ergebnis["befunde"].append(f"{breite}px {name}: waagerechter Scroll {masse}")
alarme = seite.get_by_role("alert").count()
if alarme:
ergebnis["befunde"].append(f"{breite}px {name}: {alarme} Alarm(e): {seite.get_by_role('alert').first.inner_text()[:160]}")
seite.screenshot(path=str(ziel / f"{name}-{breite}.png"), full_page=True)
for ansicht in ("Läufe", "Markt"):
seite.get_by_role("button", name=ansicht, exact=True).click()
seite.wait_for_timeout(1500)
if ansicht == "Markt":
# Die Ansicht laedt nach Plan nichts von selbst: erst die Auswahl holt Kerzen.
eintrag["canvas"] = {}
for kurz, bar_type in MARKT_DATENSAETZE:
try:
seite.get_by_label("Datensatz").select_option(bar_type)
seite.wait_for_selector("canvas", timeout=20000)
seite.wait_for_timeout(2000)
except PlaywrightTimeout:
pass
probe = seite.evaluate(CANVAS_PROBE)
eintrag["canvas"][kurz] = probe
if probe["farben"] < 5:
ergebnis["befunde"].append(f"{breite}px Markt/{kurz}: kein gezeichnetes Canvas ({probe})")
messe(f"markt-{kurz}")
else:
eintrag["laeufe_zeilen"] = seite.locator("table tbody tr").count()
if eintrag["laeufe_zeilen"] == 0:
ergebnis["befunde"].append(f"{breite}px Laeufe: keine Tabellenzeile")
messe("laeufe")
for kurz, marke in LAEUFE_EINZEL:
try:
seite.get_by_role("button", name=marke, exact=True).click(timeout=8000)
seite.wait_for_load_state("networkidle", timeout=20000)
seite.wait_for_timeout(1200)
except PlaywrightTimeout:
ergebnis["befunde"].append(f"{breite}px Laeufe/{kurz}: Knopf '{marke}' nicht bedienbar")
continue
eintrag[f"einzel_{kurz}_tabellen"] = seite.locator("table").count()
if eintrag[f"einzel_{kurz}_tabellen"] < 2:
ergebnis["befunde"].append(f"{breite}px Laeufe/{kurz}: die Einzelansicht zeigt keine eigene Tabelle")
messe(f"laeufe-{kurz}")
eintrag["konsole"] = meldungen
if any(m.startswith(("error", "pageerror")) for m in meldungen):
ergebnis["befunde"].append(f"{breite}px: Konsolenfehler {meldungen[:3]}")
ergebnis["breiten"][str(breite)] = eintrag
seite.close()
browser.close()
(ziel / "ergebnis.json").write_text(json.dumps(ergebnis, indent=2, ensure_ascii=False), encoding="utf-8")
print(json.dumps(ergebnis, indent=2, ensure_ascii=False))
return 2 if ergebnis["befunde"] else 0
if __name__ == "__main__":
sys.exit(main())