From ffbdd5170c67e569567be6f4c3640cef51edb371 Mon Sep 17 00:00:00 2001 From: claude-dev Date: Sun, 2 Aug 2026 19:26:27 +0000 Subject: [PATCH] refactor(studio): ein Startpunkt statt sechs, Schritte werden zur Anzeige MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Bausteinspalte mischte vier Dinge in einem Kartenlayout. Ergebnisse zum Anschauen, Erzeuger zum Starten, ein Artefakt ohne Erzeuger und reine Aktionen. In jeder Karte lagen Öffnen und Neurechnen wenige Pixel auseinander, beide mit demselben Kreispfeil-Symbol. Dazu kamen sechs Stellen, an denen ein Lauf beginnen konnte, verteilt über zwei Spalten. Jetzt drei getrennte Bereiche. Oben wird gearbeitet, in der Mitte werden Ergebnisse angesehen, unten stehen die Fall-Aktionen. Gearbeitet wird über genau einen Knopf, dessen Beschriftung sich am Zustand ausrichtet. Beim leeren Fall Lauf starten, bei einer Recherche Recherche starten, bei veralteten Ergebnissen Jetzt aktualisieren, sonst Erneut durchlaufen, während eines abbrechbaren Laufs Abbrechen. Darüber steht, was gerade zu tun ist, etwa die Zahl der neuen Artikel seit dem letzten Bericht. Darunter die vier Schritte als Kette mit Zustand. Sie starten nichts, Aufklappen erklärt nur, was ein Schritt tut. Damit fällt auch eine Kostenfalle weg. Ein einzeln gestartetes Sammeln buchte denselben Satz wie der komplette Lauf, wer danach Lagebild und Faktencheck einzeln nachzog, zahlte 69 statt 45 Credits, ohne dass die Oberfläche darauf hinwies. Benannt wird ab jetzt das Ergebnis statt des Erzeugers. Artikel holen, Lagebild schreiben, Fakten prüfen, Orte erkennen. Bei Recherche-Lagen heißt der zweite Schritt Recherchebericht schreiben. Die Wörter Baustein, Geoparsing und Analyse verschwinden aus der Oberfläche. Weiter entfernt sind die Karte Netzwerkanalyse, solange es den Baustein im Server nicht gibt, der Sammeln-Knopf aus der Quellenspalte und die Wahl Nur Quellen sammeln im Startdialog. Der Server behält seine Schnittstelle für einzelne Bausteine. Die Anzeige eines so gestarteten Laufs bleibt erhalten, er kann aus der klassischen Ansicht kommen. Ein solcher Lauf wird beim Abbruch auch als der Schritt abgebrochen, der wirklich läuft, nicht pauschal als kompletter Lauf. 51 tote CSS-Regeln entfernt. Prüfungen ohne Browser, 59 neue Fälle für die Lauf-Anzeige plus 12 in der Strukturprüfung, die festhalten, dass es im ganzen Dokument genau ein Element gibt, das einen Lauf starten kann. Die Anzeige der Credits am Knopf kommt später, sie ist bewusst nicht Teil dieser Änderung. Cache-Buster auf 20260802c. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 5 +- src/static/css/studio.css | 392 +++++++++++++++++++--------------- src/static/dashboard.html | 6 +- src/static/js/studio.js | 387 ++++++++++++++++++++++----------- src/static/studio.html | 214 ++++++++----------- tests/test_lauf.js | 314 +++++++++++++++++++++++++++ tests/test_studio_struktur.py | 31 ++- 7 files changed, 913 insertions(+), 436 deletions(-) create mode 100644 tests/test_lauf.js diff --git a/CLAUDE.md b/CLAUDE.md index 7423c26..d6cacbc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -136,7 +136,7 @@ src/: static/: index.html: "Login-Seite (Magic-Link), JS inline" dashboard.html: "Hauptdashboard (Sidebar + Reiter-Ansicht + 8 Modals + Chat-Widget)" - studio.html: "Studio (3-Spalten-Werkstatt, online nur fuer info@ sichtbar - Gating rein clientseitig). Reiter Einstellungen ersetzt das Bearbeiten-Modal der klassischen Ansicht und enthaelt die Wahl des KI-Wegs je Fall." + studio.html: "Studio (3-Spalten-Werkstatt, online nur fuer info@ sichtbar - Gating rein clientseitig). Reiter Einstellungen ersetzt das Bearbeiten-Modal der klassischen Ansicht und enthaelt die Wahl des KI-Wegs je Fall. Spalte 3 hat genau EINEN Startpunkt (#ls-btn), darunter stehen die vier Schritte als reine Anzeige. Einzelne Bausteine lassen sich aus der Oberflaeche nicht mehr starten, der Server kann es weiterhin." css/: style.css: "AegisSight Design System (Dark/Light Theme, alle Komponenten)" studio.css: "Studio-spezifische Styles" @@ -144,7 +144,7 @@ src/: api.js: "REST-API-Client (fetch, Auth-Header, 30s Timeout, 403 -> Nur-Lese-Modus)" app.js: "Hauptlogik: ThemeManager, NotificationCenter, App-Objekt (181 KB)" components.js: "UI-Rendering: Sidebar, Faktenchecks, Toasts, Progress, Karte (geteilt mit Studio)" - studio.js: "Kompletter Studio-Controller (116 KB), enthaelt auch den Einstellungen-Reiter je Fall" + studio.js: "Kompletter Studio-Controller, enthaelt den Einstellungen-Reiter je Fall und die Lauf-Anzeige (_renderLauf, _schrittZustaende, laufKnopf)" chat.js: "Chat-Assistent Widget (Bedienfragen)" pipeline.js: "Grafische Analysepipeline (11 Schritte, Live-Animation)" layout.js: "Reiter-Umschalter je Lage (merkt letzten Reiter; enthaelt noch gridstack-Legacy-Stubs)" @@ -171,6 +171,7 @@ tests/: test_sammelaktionen.js: "Sammelaktionen der Seitenleiste im klassischen Dashboard, Auswahlmodus, Filterbeachtung, Loeschbestaetigung (Aufruf mit node)" test_falleinstellungen.js: "Einstellungen-Reiter im Studio, Formular fuellen, nur Geaendertes speichern, KI-Weg umstellen samt Warnhinweis, Abo nachladen (Aufruf mit node)" test_studio_struktur.py: "Strukturpruefung beider Oberflaechen ohne Browser, eindeutige IDs, jedes onclick zeigt auf eine vorhandene Methode, Cache-Buster, Design-Tokens" + test_lauf.js: "Lauf-Anzeige der Studio-Spalte, Zustand des Startknopfs, Kette, Schrittzustaende aus /freshness, Nachweis dass kein Schritt etwas startet (Aufruf mit node)" test_quellenausgabe.py: "Ausgabetreue des Quellenverzeichnisses: Nummern aus dem Lagebild statt Zeilennummern, keine Kappung, ein Verlag ein Name, Statistik deckungsgleich mit dem Verzeichnis" ``` diff --git a/src/static/css/studio.css b/src/static/css/studio.css index b183d15..9796bb8 100644 --- a/src/static/css/studio.css +++ b/src/static/css/studio.css @@ -1014,180 +1014,6 @@ es neu, die Zeile darunter meldet den Datenstand (aktuell / veraltet / laeuft). */ @keyframes st-spin { to { transform: rotate(360deg); } } -/* Kompletter Lauf: orchestriert alles, daher als Primaerknopf ueber den Karten */ -.run-all { - display: flex; align-items: center; justify-content: center; gap: 8px; - width: 100%; - padding: 9px 12px; - margin-bottom: 12px; - border-radius: var(--radius-md, 6px); - border: 1px solid var(--accent); - background: var(--accent); - color: #fff; - font-family: var(--font-title); - font-weight: 600; - font-size: 12.5px; - cursor: pointer; - transition: background .15s, opacity .15s; -} -.run-all:hover:not(:disabled) { background: var(--accent-hover); } -.run-all:disabled { opacity: .45; cursor: not-allowed; } -/* display:flex wuerde das hidden-Attribut ueberstimmen - explizit ausblenden */ -.run-all[hidden] { display: none; } -.run-all .ra-icon { display: inline-flex; } -.run-all .ra-state { display: none; } -.run-all.running .ra-icon { display: none; } -.run-all.running .ra-state { - display: inline-flex; align-items: center; gap: 6px; font-weight: 400; font-size: 11.5px; -} -.run-all.running .ra-state::before { - content: ''; width: 11px; height: 11px; flex-shrink: 0; - border: 2px solid currentColor; border-right-color: transparent; - border-radius: 50%; animation: st-spin .8s linear infinite; -} - -/* Die Karte selbst */ -.art-card { - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: 10px; - margin-bottom: 8px; - overflow: hidden; - transition: border-color .15s; -} -.art-card:hover { border-color: var(--accent); } -.art-card:has(.studio-menu-item.active) { border-color: var(--accent); } -.art-card.busy { border-color: var(--accent); } -.art-card-soon { opacity: .6; } -.art-row { display: flex; align-items: stretch; } - -/* Kartenkopf: uebernimmt die Rolle des alten Menue-Eintrags, aber ohne eigenen Rahmen */ -.studio-menu .art-head { - flex: 1; min-width: 0; - display: flex; align-items: center; gap: 10px; - padding: 10px var(--sp-lg); - margin: 0; - background: none; - border: none; - border-radius: 0; - text-align: left; - cursor: pointer; - color: var(--text-primary); - font-family: var(--font-body); -} -.studio-menu .art-head:hover { background: var(--bg-hover); } -.studio-menu .art-head.active { background: var(--tint-accent-faint); } -.studio-menu .art-head-static { cursor: default; } -.studio-menu .art-head-static:hover { background: none; } -.art-head .mi-icon { flex-shrink: 0; width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; color: var(--accent); } -.art-head .mi-text { flex: 1; min-width: 0; } -.art-head .mi-title { - display: block; - font-family: var(--font-title); - font-weight: 600; - font-size: 13px; - line-height: 1.25; - white-space: normal; - overflow: visible; - text-overflow: clip; -} -.art-head .mi-meta { display: block; margin-top: 1px; font-size: 10.5px; color: var(--text-disabled); } - -/* Erzeuger-Aktion (rechnet das Ergebnis neu) */ -.art-run { - flex-shrink: 0; - width: 36px; - display: flex; align-items: center; justify-content: center; - background: none; - border: none; - border-left: 1px solid var(--border); - color: var(--text-disabled); - cursor: pointer; - transition: color .15s, background .15s; -} -.art-run:hover:not(:disabled) { color: var(--accent); background: var(--bg-hover); } -.art-run:disabled { opacity: .35; cursor: not-allowed; } - -/* Datenstand-Zeile */ -.art-note { - display: none; - align-items: center; - gap: var(--sp-md); - padding: 7px var(--sp-lg); - border-top: 1px solid var(--border); - font-size: 11px; - line-height: 1.35; -} -.art-note.show { display: flex; } -.art-note.fresh { background: var(--tint-success); color: var(--success); } -.art-note.stale { background: var(--tint-warning); color: var(--warning); } -.art-note.blocked { background: var(--bg-elevated); color: var(--text-disabled); } -.art-note.running { background: var(--tint-accent); color: var(--accent); } -.art-note .an-spin { - width: 11px; height: 11px; flex-shrink: 0; - border: 2px solid currentColor; border-right-color: transparent; - border-radius: 50%; animation: st-spin .8s linear infinite; -} -.art-note button { - margin-left: auto; - white-space: nowrap; - padding: 3px 9px; - font-size: 10.5px; - font-weight: 600; - border-radius: var(--radius-md, 6px); - border: 1px solid currentColor; - background: transparent; - color: inherit; - cursor: pointer; - font-family: var(--font-body); -} -.art-note button:hover { background: var(--tint-hover-subtle); } - -/* Unter-Ansichten: gehoeren zum selben Erzeuger (Analyse) */ -.art-subs { border-top: 1px solid var(--border); } -.studio-menu .art-sub { - display: flex; align-items: center; gap: 8px; - width: 100%; - padding: 6px var(--sp-lg); - margin: 0; - background: none; - border: none; - border-radius: 0; - cursor: pointer; - text-align: left; - color: var(--text-secondary); - font-family: var(--font-body); - font-size: 11.5px; -} -.studio-menu .art-sub:hover { background: var(--bg-hover); color: var(--text-primary); } -.studio-menu .art-sub.active { background: var(--tint-accent-faint); color: var(--accent); } -.art-sub .mi-icon { flex-shrink: 0; color: var(--text-disabled); display: inline-flex; } -.art-sub.active .mi-icon { color: var(--accent); } -.art-sub .mi-title { - flex: 1; min-width: 0; - font-family: var(--font-body); - font-weight: 500; - font-size: 11.5px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.art-sub .mi-meta { flex-shrink: 0; font-size: 10px; color: var(--text-disabled); } - -/* Export: Ausgabe-Aktion, kein Artefakt -> Fusszeile */ -.art-foot { margin-top: var(--sp-lg); padding-top: var(--sp-lg); border-top: 1px solid var(--border); } -.studio-menu .art-export { justify-content: center; gap: 8px; margin-bottom: 0; padding: 8px; font-size: 12px; } -.art-export .mi-title { font-family: var(--font-body); font-weight: 600; font-size: 12px; flex: 0 0 auto; } -.art-export .mi-icon { color: var(--accent); display: inline-flex; } - -/* "Sammeln" in der Quellenspalte (sein Ergebnis landet dort) */ -.collect-btn:disabled { opacity: .45; cursor: not-allowed; } -.collect-btn.running .cb-label { display: none; } -.collect-btn.running::after { - content: 'Sammelt …'; - display: inline-flex; align-items: center; -} - /* Karten-Container fuellt den Karte-Tab */ #map-container { width: 100%; @@ -1486,3 +1312,221 @@ .case-ai-tag.ai-anthropic { color: var(--text-disabled); } + +/* === Der Lauf (Spalte 3) ============================================== */ +/* Genau ein Startpunkt. Die vier Schritte darunter sind Anzeige, sie starten + nichts. Frueher standen hier Artefakt-Karten, in denen ein Klick links das + Ergebnis oeffnete und einer rechts es neu rechnete. Das lag zu nah + beieinander und kostete im falschen Fall Geld. */ + +.lauf-stand { + padding: 12px; + margin-bottom: 12px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--bg-card); +} + +/* Vollstaendig umlaufender Rahmen, kein einseitiger Streifen */ +.lauf-stand.betont { + border: 2px solid var(--sidebar-active, #C8A851); + padding: 11px; +} + +.ls-haupt { + font-size: 13px; + color: var(--text-primary); + margin-bottom: 2px; +} + +.ls-haupt.warn { color: var(--warning); } + +.ls-neben { + font-size: 11px; + color: var(--text-tertiary); +} + +.ls-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + width: 100%; + margin-top: 12px; + padding: 10px 12px; + border-radius: 6px; + border: 1px solid var(--sidebar-active, #C8A851); + background: var(--sidebar-active, #C8A851); + color: #1A1A1A; + font-family: var(--font-title, inherit); + font-size: 13px; + font-weight: 600; + cursor: pointer; + transition: filter .15s, opacity .15s; +} + +.ls-btn:hover:not(:disabled) { filter: brightness(1.08); } + +.ls-btn:disabled { opacity: .45; cursor: not-allowed; } + +.ls-btn.abbrechen { + background: transparent; + border-color: var(--error); + color: var(--error); +} + +.ls-icon { display: inline-flex; flex-shrink: 0; } + +.ls-hinweis { + margin-top: 8px; + font-size: 11px; + color: var(--text-tertiary); + text-align: center; +} + +.ls-hinweis[hidden] { display: none; } + +/* --- Die Kette --- */ +.lauf-kette { + display: flex; + align-items: center; + padding: 4px 6px 8px; +} + +.kn { + width: 12px; + height: 12px; + flex-shrink: 0; + border-radius: 50%; + border: 2px solid var(--border); + background: var(--bg-elevated); +} + +.kn-fertig { background: var(--success); border-color: var(--success); } +.kn-veraltet { background: var(--warning); border-color: var(--warning); } +.kn-laeuft { + background: var(--sidebar-active, #C8A851); + border-color: var(--sidebar-active, #C8A851); + animation: kn-puls 1.4s ease-in-out infinite; +} +.kn-gesperrt { opacity: .45; } + +.kn-linie { flex: 1; height: 2px; background: var(--border); } +.kn-linie.aktiv { background: var(--success); } + +@keyframes kn-puls { 0%, 100% { opacity: 1; } 50% { opacity: .35; } } + +@media (prefers-reduced-motion: reduce) { + .kn-laeuft { animation: none; } +} + +.lauf-fortschritt { + font-size: 11px; + color: var(--text-tertiary); + text-align: center; + margin-bottom: 12px; +} + +/* --- Die Schritte --- */ +.lauf-schritte { margin-bottom: 4px; } + +.schritt { + border: 1px solid var(--border); + border-radius: 6px; + margin-bottom: 6px; + background: var(--bg-card); + overflow: hidden; +} + +.schritt-laeuft, .schritt-veraltet { + border: 2px solid var(--sidebar-active, #C8A851); +} + +.schritt-gesperrt { opacity: .55; } + +.schritt-kopf { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 8px 10px; + background: none; + border: none; + color: var(--text-primary); + font-family: inherit; + text-align: left; + cursor: pointer; +} + +.schritt-kopf:hover { background: var(--tint-hover-subtle, rgba(255,255,255,.03)); } + +.schritt-nr { + flex-shrink: 0; + width: 20px; + height: 20px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + font-weight: 700; + background: var(--bg-elevated); + color: var(--text-tertiary); +} + +.schritt-fertig .schritt-nr { background: var(--tint-success); color: var(--success); } +.schritt-veraltet .schritt-nr { background: var(--tint-warning); color: var(--warning); } +.schritt-laeuft .schritt-nr { background: var(--tint-accent); color: var(--sidebar-active, #C8A851); } + +.schritt-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; } +.schritt-name { font-size: 12px; } +.schritt-stand { font-size: 11px; color: var(--text-tertiary); } +.schritt-fertig .schritt-stand { color: var(--success); } +.schritt-veraltet .schritt-stand { color: var(--warning); } + +.schritt-pfeil { + flex-shrink: 0; + color: var(--text-tertiary); + display: inline-flex; + transition: transform .18s; +} + +.schritt.offen .schritt-pfeil { transform: rotate(180deg); } + +.schritt-mehr { + display: none; + padding: 0 10px 10px 40px; + font-size: 11px; + line-height: 1.5; + color: var(--text-secondary); +} + +.schritt.offen .schritt-mehr { display: block; } + +/* --- Bereichstrenner im Menue --- */ +.menu-bereich { + display: flex; + align-items: center; + gap: 8px; + margin: 20px 0 6px; + font-size: 10px; + font-weight: 700; + letter-spacing: .08em; + text-transform: uppercase; + color: var(--text-tertiary); +} + +.menu-bereich::after { + content: ""; + flex: 1; + height: 1px; + background: var(--border); +} + +/* Untergeordnete Ansicht, gehoert zum Eintrag darueber */ +.studio-menu-item.mi-unter { + padding-left: 30px; + font-size: 12px; +} + +.studio-menu-item.mi-unter .mi-title { font-size: 12px; } diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 6b57189..9c16903 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -13,7 +13,7 @@ - +