fix: <style>-Blöcke aus allen 24 Artikeln entfernt + Skill korrigiert

- artikel_*.html: 24 Dateien von <style>-Blöcken befreit (body, max-width zerstören Joomla)
- journalistic-article-writing SKILL.md: falsche 'AUSNAHME 09.07.' entfernt
- Ursache: Skill-Kommentar erlaubte Sub-Agenten <style> 'für Vorschau' — kein Gatekeeper existiert
This commit is contained in:
Hermes Agent
2026-07-09 23:30:55 +02:00
parent 9d3461750c
commit 2a06bcaee0
144 changed files with 249452 additions and 9229 deletions
+115 -1
View File
@@ -528,7 +528,7 @@ def build_index(articles: list[dict]) -> str:
<header>
<div class="header-inner">
<h1>Artikel-Übersicht</h1>
<p class="subtitle">{count} Artikel im Archiv · <a href="/?key={ACCESS_TOKEN}">Aktualisieren</a> · <a href="/nanobanana?key={ACCESS_TOKEN}" style="font-weight:600">🎨 Bild-Generator</a><span class="token-indicator">{token_status}</span></p>
<p class="subtitle">{count} Artikel im Archiv · <a href="/?key={ACCESS_TOKEN}">Aktualisieren</a> · <a href="/nanobanana?key={ACCESS_TOKEN}" style="font-weight:600">🎨 Bild-Generator</a> · <a href="/fehler?key={ACCESS_TOKEN}" style="font-weight:600">🐛 Fehler</a><span class="token-indicator">{token_status}</span></p>
</div>
</header>
<div class="container">
@@ -920,6 +920,12 @@ h1{color:#c0392b;}p{color:#888;}</style></head>
self.wfile.write(html.encode("utf-8"))
elif path == "/nanobanana/articles":
self._handle_nanobanana_articles()
elif path == "/fehler":
html = self._build_fehler_page()
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(html.encode("utf-8"))
elif path.startswith("/media/"):
rel = path[len("/"):]
filepath = os.path.normpath(os.path.join(WORKSPACE, rel))
@@ -997,6 +1003,12 @@ h1{color:#c0392b;}p{color:#888;}</style></head>
if not self._check_token():
return
self._handle_nanobanana_assign()
elif path == "/fehler":
html = self._build_fehler_page()
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(html.encode("utf-8"))
elif path == "/delete":
if not self._check_auth():
return
@@ -1302,6 +1314,108 @@ h1{color:#c0392b;}p{color:#888;}</style></head>
save_image_map(img_map)
self._json_reply(200, {"ok": True, "image": image_path, "article": article_file})
def _build_fehler_page(self) -> str:
"""Baut die Fehler-Dashboard-Seite mit Daten vom Fehler-Server."""
stats = {"total": 0, "offen": 0, "behoben": 0, "verworfen": 0}
meldungen = []
try:
req = urllib.request.Request("http://127.0.0.1:8888/api/stats")
with urllib.request.urlopen(req, timeout=5) as resp:
stats = json.loads(resp.read())
except Exception as e:
stats["error"] = str(e)
try:
req = urllib.request.Request("http://127.0.0.1:8888/api/meldungen")
with urllib.request.urlopen(req, timeout=5) as resp:
meldungen = json.loads(resp.read())
except Exception:
pass
status_labels = {
"offen": '<span style="background:#fef3c7;color:#92400e;">offen</span>',
"in_bearbeitung": '<span style="background:#dbeafe;color:#1e40af;">in Bearbeitung</span>',
"behoben": '<span style="background:#def7ec;color:#065f46;">behoben</span>',
"verworfen": '<span style="background:#f3f4f6;color:#6b7280;">verworfen</span>',
}
rows = ""
for m in meldungen[:200]:
text = (m.get("fehler_text") or "")[:100]
rows += (
'<tr><td>{id}</td>'
'<td><a href="https://www.brettspiel-news.de/index.php/de/component/content/article/{article_id}" target="_blank">#{article_id}</a></td>'
'<td>{name}</td><td title="{full}">{text}</td>'
'<td>{status}</td><td style="color:#6b7280;font-size:0.8rem;">{loesung}</td>'
'<td style="font-size:0.8rem;">{date}</td></tr>'
).format(
id=m.get("id"),
article_id=m.get("article_id"),
name=m.get("melder_name", ""),
text=text + ("…" if len(m.get("fehler_text") or "") > 100 else ""),
full=(m.get("fehler_text") or "").replace('"', "&quot;"),
status=status_labels.get(m.get("status"), m.get("status", "")),
loesung=m.get("loesung") or "—",
date=(m.get("created_at") or "")[:10],
)
if not rows:
rows = '<tr><td colspan="8" style="padding:20px;text-align:center;color:#9ca3af;">Keine Meldungen</td></tr>'
return """<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fehlermeldungen - BSN</title>
<style>
:root {{ --bsn: #20228a; --bg: #F9FAFB; --card: #fff; --border: #e5e7eb; --text: #1F2937; }}
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); line-height: 1.5; padding: 20px; max-width: 1200px; margin: 0 auto; }}
header {{ background: var(--bsn); color: #fff; padding: 20px 24px; border-radius: 10px; margin-bottom: 20px; }}
header h1 {{ font-size: 1.4rem; }}
header a {{ color: #a5b4fc; font-size: 0.9rem; }}
.stats {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px; margin-bottom: 20px; }}
.stat {{ background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 14px; text-align: center; }}
.stat .num {{ font-size: 1.8rem; font-weight: 800; }}
.stat .lbl {{ font-size: 0.75rem; color: #6b7280; margin-top: 2px; }}
table {{ width: 100%; border-collapse: collapse; background: var(--card); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }}
th {{ background: #f8fafc; padding: 8px 10px; font-size: 0.8rem; text-align: left; font-weight: 600; }}
td {{ padding: 6px 10px; border-bottom: 1px solid var(--border); font-size: 0.85rem; }}
tr:last-child td {{ border-bottom: none; }}
.c-total {{ color: var(--bsn); }} .c-offen {{ color: #f39c12; }} .c-behoben {{ color: #27ae60; }} .c-verworfen {{ color: #9ca3af; }}
span[style] {{ display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 0.7rem; font-weight: 600; }}
</style>
</head>
<body>
<header>
<h1>🐛 Fehlermeldungen</h1>
<a href="/?key={ACCESS_TOKEN}">← Zurück zur Artikel-Übersicht</a>
</header>
<div class="stats">
<div class="stat"><div class="num c-total">{total}</div><div class="lbl">Gesamt</div></div>
<div class="stat"><div class="num c-offen">{offen}</div><div class="lbl">Offen</div></div>
<div class="stat"><div class="num c-behoben">{behoben}</div><div class="lbl">Behoben</div></div>
<div class="stat"><div class="num c-verworfen">{verworfen}</div><div class="lbl">Verworfen</div></div>
</div>
<table>
<thead><tr>
<th>ID</th><th>Artikel</th><th>Name</th><th>Fehler</th>
<th>Status</th><th>Lösung</th><th>Datum</th>
</tr></thead>
<tbody>{rows}</tbody>
</table>
</body>
</html>""".format(
ACCESS_TOKEN=ACCESS_TOKEN,
total=stats.get("total", 0),
offen=stats.get("offen", 0),
behoben=stats.get("behoben", 0),
verworfen=stats.get("verworfen", 0),
rows=rows,
)
def _handle_delete(self):
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)