Beim ersten Schritt (sources_review) eines neuen Refreshs werden alle
nachfolgenden Schritte sichtbar auf "pending" (grau) zurückgesetzt.
Vorher hingen sie weiterhin als "done" vom letzten Refresh in grün
herum, während die Pipeline schon einen neuen Durchlauf zeigte.
- Bedingung in pipeline.js entschärft: nicht mehr nur bei
pass_number > 1 (Multi-Pass), sondern bei jedem ersten Schritt-Active
- Bei Reset wird das ganze Stage neu gezeichnet (nicht nur der einzelne
Block), damit die zurückgesetzten Schritte tatsächlich grau erscheinen
- Greift sowohl bei normalem Refresh als auch bei Multi-Pass-Wechsel
einer Research-Lage
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reihenfolge in der Pipeline-Anzeige getauscht — passt zur perspektivischen
Backend-Umstellung (Faktencheck-Output soll als Kontext ins Lagebild
einfließen, statt parallel zu generieren). Backend läuft aktuell noch
parallel; sobald die sequenzielle Variante mit Kontext-Übergabe steht,
stimmt die Anzeige mit dem realen Flow überein.
Im 3x3-Snake-Layout liegt jetzt:
Reihe 2: Relevanz bewerten → Orte erkennen → Fakten prüfen
Reihe 3: Lagebild verfassen → Qualitätscheck → Benachrichtigen
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Die Reputation-Map nutzte veraltete Schlüssel (presseagenturen,
behoerden, nachrichten_de/int), die nirgends in der DB vorkamen — die
DB hat nachrichtenagentur, behoerde, oeffentlich-rechtlich,
qualitaetszeitung, think-tank, regional, telegram, boulevard. Folge
war ein stiller Bug: alle hochwertigen Quellen (Reuters, ZDF,
tagesschau, Spiegel, FAZ, BMI etc.) bekamen den Default-Score 0.4 wie
"sonstige" und wurden in der Relevanz-Sortierung nicht bevorzugt.
Map jetzt vollständig auf aktuelle Kategorie-Werte:
- nachrichtenagentur, behoerde: 1.00
- oeffentlich-rechtlich: 0.95
- qualitaetszeitung, think-tank: 0.85
- fachmedien: 0.80
- international: 0.75
- regional: 0.65
- telegram: 0.50
- sonstige: 0.40
- boulevard: 0.30
Test mit 200 zufälligen Artikeln aus der Live-DB:
155 besser bewertet, 0 schlechter, 45 unverändert.
Stärkster Effekt bei ÖR (+0.165), Nachrichtenagenturen (+0.18),
Qualitätszeitungen (+0.135).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Statt einer eigenen Nummerierung (1., 2., ...) wird jetzt die echte
Lagebild-Quellennummer im Format [N] angezeigt — also exakt das, was im
Lagebild-Text als Zitat erscheint. Match per exakter source_url, mit
Quellen-Name als Fallback.
Artikel ohne Match (nicht im Lagebild zitiert) bekommen einen dezenten
Strich "—" mit Tooltip "Nicht im Lagebild zitiert", damit sichtbar ist
welche Belege Claude überhaupt verwendet hat und welche nicht.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aufklapp-Liste pro Quelle zeigt jetzt:
1. fortlaufende Nummer (gold, monospace)
2. Datum + Uhrzeit (klein, dezent grau, monospace)
3. Headline als Link zum Originalartikel
Drei-Spalten-Grid (Nummer | Datum | Headline). Auf schmalem Viewport
(<600px) klappt das Datum unter die Nummer. Bei research-Lagen wird
published_at bevorzugt, sonst collected_at.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Quellen-Boxen waren bisher reine Anzeige. Jetzt sind sie klickbar:
beim Klick erscheint direkt unter der Box (über die volle Grid-Breite)
eine Liste der Artikel-Headlines dieser Quelle, jede mit Link zum
Originalartikel. Mutual-exclusive — Klick auf eine andere Quelle
schließt die vorherige automatisch.
- components.js: Item bekommt data-source, onclick + Tastatur-Support
(Enter/Space), aria-expanded.
- app.js: toggleSourceOverviewDetail filtert _currentArticles nach
Quelle, sortiert chronologisch absteigend, fügt das Detail-Element
via insertAdjacentElement direkt nach dem geklickten Item ein.
- CSS: aktiver Item-Status (Glow + Tint), Detail-Block mit
grid-column 1/-1 (volle Breite) + max-height 320px scrollbar bei
vielen Artikeln + dezente Slide-In-Animation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bisher musste eine Headline mindestens 2 der dynamisch generierten
Suchworte enthalten, um den Match-Filter zu passieren. Bei thematisch
engen Lagen (Bsp. "Buckelwal timmy") fielen damit echte Treffer wie
"Transport mit Buckelwal erreicht dänische Gewässer..." durch, weil
nur 1 Keyword (buckelwal) gematcht hat.
Neue Heuristik: enthält der Text mindestens ein spezifisches Keyword
(>=7 Zeichen, also keine kurzen Akteursnamen wie "iran" oder "trump"),
reicht 1 Treffer. Bei nur kurzen, generischen Keywords gilt weiter die
alte Schwelle (halb der Wörter, max. 2). Topic-Filter danach (Haiku)
fängt False Positives.
Damit kommen ZDF/tagesschau/n-tv-Headlines mit nur einem starken
Begriff durch — der Hauptgrund, warum Lage 8 Buckelwal mit ZDF-Quelle
am ersten Refresh 0 Artikel hatte.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bisher hatten Quellen vom Typ web_source keine praktische Wirkung auf
die Recherche - sie lagen nur als Marker in der DB. Jetzt werden sie
aktiv in den Recherche-Prompt eingebunden.
Ablauf:
1. Vor dem Hauptaufruf an Opus prüft ein günstiger Haiku-Call alle
aktiven Web-Quellen des Tenants (plus globale) und wählt die
thematisch passenden aus. Leere Selektion ist ausdrücklich erlaubt.
2. Die ausgewählten Domains werden dem Recherche-Prompt als
"EINGETRAGENE WEB-QUELLEN" Block beigegeben mit der Empfehlung,
gezielt mit "site:domain query" zu suchen, falls thematisch passend.
3. site: ist Empfehlung, kein Zwang - Claude bleibt flexibel und
ergänzt seine sonstige Recherche.
- source_rules.get_feeds_with_metadata: SELECT um notes-Feld erweitert,
damit der Selektor besseren Kontext zur Quelle hat.
- ResearcherAgent.select_relevant_web_sources: neuer Helper analog zu
select_relevant_feeds, mit Skip-Optimierung wenn ≤3 Quellen.
- WEB_SOURCE_SELECTION_PROMPT: explizite Regel "lieber leer als
pauschal alle", verhindert Token-Verschwendung.
- ResearcherAgent.search: neuer Parameter preferred_sources, beide
Templates (RESEARCH + DEEP_RESEARCH) bekommen optionalen
preferred_sources_block.
- Orchestrator._web_search_pipeline: Vorselektion vor researcher.search,
Token-Usage in usage_acc, Logging der gewählten Domains.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der Pfeil überschattete das darunter liegende Stunden-Label. Goldener
Balken mit Glow + scaleY reicht als visuelles Aktiv-Signal.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inline-onclick mit JSON.stringify(label) + UI.escape erzeugte bei
Bucket-Labels mit Anführungszeichen oder Sonderzeichen einen kaputten
HTML-Attribut-String. Klicks lösten daher gar keinen Handler aus.
Statt JS-String im onclick werden Bucket-Daten jetzt als
data-start/data-end/data-label-Attribute am Cell-Element gehalten.
Onclick ruft App.handleStripClick(this), das die Werte sauber aus
dataset liest und an openTimelineWindow weiterreicht.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorher scrollte ein Klick auf einen Balken nur zur passenden Zeit-
Gruppe — bei langem Stream kaum erkennbar. Jetzt filtert der Klick
den Stream auf das Zeitfenster des Balkens und zeigt nur diese
Einträge.
- Aktiver Balken: vergrößert (scaleY 1.6) + goldener Hintergrund +
starker Glow + kleiner ▼-Pfeil darunter; alle anderen Balken auf
40% Opacity gedimmt.
- Banner zwischen Strip und Stream zeigt "Gefiltert auf [Label] ·
X Einträge" mit "Filter aufheben"-Button.
- Zweiter Klick auf denselben Balken oder Banner-Button hebt den
Filter auf.
- Filter/Range-Buttons setzen den Strip-Window-Filter zurück (sonst
inkonsistente Doppel-Filterung).
- Lagen-Wechsel räumt _activeStripWindow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Komplett neu gedacht: nicht mehr horizontale Karten-Kette, sondern
vertikaler Newsfeed mit den vorhandenen vt-Klassen, plus dezenter
Heatmap-Strip oben für die Quantitäts-Übersicht.
- Heatmap-Strip oben (14 px hoch): ein Quadrat pro Tag/Stunde/Woche/
Monat je nach Spannweite, Farbintensität = Aktivität, goldener
Boden-Strich bei Lagebericht.
- Klick auf Heatmap-Quadrat: Stream scrollt zur passenden Zeit-Gruppe,
diese flasht kurz auf.
- Newsfeed darunter: vt-time-group mit Datums-Trennzeilen
(Heute/Gestern/...), Lagebericht-Einträge sind durch ihre vt-snapshot
Klasse prominent gegenüber Meldungs-Einträgen.
- Klick auf Lagebericht: Volltext klappt inline auf (vorhandener
lazyLoadSnapshotDetail-Mechanismus, kein separates Detail-Panel mehr).
- Klick auf Meldung: Detail klappt inline auf.
Karten-Kette, Verbindungs-Stränge, "Aktuell"-Marker, Snapshot-Detail-
Panel, Window-Detail-Panel und alle zugehörigen CSS-Klassen
(ht-card/ht-link/ht-now/ht-chain/ht-detail) komplett entfernt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Komplette Neufassung der horizontalen Timeline. Lageberichte sind die
natürlichen Anker einer OSINT-Lage; Artikel werden um sie herum
gruppiert.
Aufbau:
- Quanti-Strip oben: schmale Heatmap-Reihe (ein Quadrat pro Stunde/Tag/
Woche/Monat je nach Spannweite), Farbintensität = Aktivität. Quadrate
mit Lagebericht haben goldene Unterkante. Klick auf Quadrat öffnet
Detail-Panel mit allen Meldungen des Zeitfensters.
- Lagebericht-Kette darunter: jede Karte zeigt Datum, Vorschau-Text aus
dem Snapshot, Anzahl Meldungen + Fakten. Karten sind durch Stränge
verbunden, die "X Meldungen"-Pille tragen — Klick auf Strang öffnet
Liste der Meldungen zwischen den beiden Lageberichten.
- "Aktuell"-Marker am rechten Ende mit pulsierendem Pin.
Filter:
- Alle: Strip + Kette
- Meldungen: Strip + vertikaler Stream
- Lageberichte: nur Karten ohne Strip/Stränge
Edge-Case: Lagen ohne Lagebericht zeigen Strip + Stream als Fallback.
Mobile (<900px): Kette stapelt vertikal, Strip bleibt horizontal.
Alte Bar-Achse, Punkte, Bucket-Merge, Day-Markers etc. komplett
entfernt — die alte Achse war für sporadische OSINT-Aktivität das
falsche Pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Browser hatten die alten Timeline-Stile gecached und Änderungen waren
nicht sichtbar. Versions-Suffixe auf 20260501a aktualisiert, damit der
Cache zwingend invalidiert wird.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mehrere Snapshots in derselben Achsen-Position erzeugten verschmierte,
übereinanderliegende Pin-Symbole. Zusätzlich war der goldene Streifen
auf der Bar (Bar-Cap) redundant zur vertikalen Snapshot-Linie.
- Snapshots werden pro Achsen-Position (auf 0,5%-Genauigkeit) gruppiert.
Eine einzige Linie + ein einziger Pin pro Gruppe; bei mehreren
Lageberichten zeigt der Pin die Anzahl als Zahl statt das Stempel-
Symbol.
- Bar-Cap (separates Element über der Bar) entfernt. Stattdessen
bekommt die Bar-Füllung bei has-snapshot eine dezente goldene
Top-Linie via ::before — keine Doppel-Markierung mehr.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Im Top-Bereich der Achse kollidierten Tagesmarker, Themen-Labels und
Lagebericht-Stempel auf der gleichen Höhe. Jetzt klare Schichten:
- Tagesmarker (Heute/Gestern/Datum): top 0
- Themen-Labels: eigene Schiene direkt darunter (top 22 / 42 hourly),
nicht mehr Kind der Bar — verhindert Wandern bei verschieden hohen
Bars
- Bars: nach unten verschoben (top 44 / 64 hourly)
- Lagebericht-Linien: gehen jetzt nur durch den Bar-Bereich,
Pin-Symbol (Cap) hängt UNTEN an der Achsenlinie statt oben in den
Tagesmarkern
- Heute-Linie: bei stündlicher Granularität ausgeblendet (Tagesmarker
zeigt eh "Heute, ..."), bei Tag/Woche/Monat weiterhin aktiv
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Punkte ersetzt durch schmale Säulen (Bar-Chart), Höhe = Anzahl Artikel
im Bucket relativ zum Maximum. Aktivität ist sofort als Verlauf lesbar.
- Granularität: hour < 48h, day < 30T, week < 180T, sonst month.
Bucket-Merge (verfälscht das Datum) entfernt, stattdessen sauberer
Granularitätswechsel.
- Lagebericht-Linien quer durch die Achse als dezente goldene Vertikalen
mit kleinem Stempel-Symbol oben. Klick öffnet das Bucket-Detail mit
dem zugehörigen Snapshot.
- Heute-Linie mit Label, wenn der heutige Zeitpunkt im sichtbaren
Bereich liegt.
- Themen-Label über den Top-3 aktivsten Buckets: clientseitig per
Wort-Häufigkeit aus Headlines, mit deutscher Stopwortliste. Zeigt
nur, wenn ein Wort mindestens zweimal vorkommt.
- Hover über eine Säule: Mini-Karte mit den 3 relevantesten Headlines
des Buckets (sortiert nach relevance_score), plus "+N weitere" und
Lagebericht-Hinweis bei gemischten Buckets.
- Snapshot-Bars bekommen oben einen goldenen Cap als Marker.
- Reduced-motion respektiert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Track war 100% breit, dadurch saß die ltr-Reihe links und die rtl-Reihe
rechts in der Karte. Block 3 (Ende Reihe 1) und Block 4 (Anfang Reihe 2)
lagen weit auseinander, der Reihenwechsel-Pfeil wirkte zusammenhanglos.
Track ist jetzt inline-flex (schrumpft auf Inhaltsbreite, ca. 3 Blöcke
plus Lücken) und wird in der Karte zentriert. Alle drei Reihen sind
gleich breit, Block 3 sitzt direkt über Block 4, die Snake-Form wird
optisch nachvollziehbar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Der U-Turn-Bogen, der quer ueber die ganze Box-Breite lief, wirkte bei
nur drei Bloecken pro Reihe optisch ueberladen. Jetzt sitzt unter dem
letzten Block der oberen Reihe ein schlichter, kurzer Pfeil nach unten,
der direkt zum ersten Block der naechsten Reihe zeigt.
- pipeline.js: Neue _renderUturn-Variante, die Spacer (Block-Breite)
vor oder hinter den Pfeil setzt, sodass er passgenau unter dem letzten
Block sitzt (rechts nach ltr-Reihe, links nach rtl-Reihe).
- style.css: Pfeil-Container nutzt Flex mit Block-Breite-Spacern statt
100%-breitem SVG. Kurzer ↓ als gerader Pfad mit Pfeilkopf,
is-flowing-Animation bleibt erhalten.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Em-dashes und Umlaut-Umschreibungen aus den Pipeline-Aenderungen
entfernt: Tooltip-Texte, HTML-Empty-State, JS-Kommentare,
Count-Status-Platzhalter, Orchestrator-Kommentare und CSS-Kommentare.
Anstelle von typografischen Gedankenstrichen werden jetzt Kommas oder
Punkte gesetzt, "uebersprungen" -> "uebersprungen" mit echtem Umlaut,
"laeuft" usw. analog. UI-Text "— Refresh starten" wird zu zwei
Saetzen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pipeline laeuft jetzt zickzack: Reihe 1 von links nach rechts, U-Turn
nach unten, Reihe 2 von rechts nach links, U-Turn nach unten,
Reihe 3 wieder von links nach rechts. Karte waechst auf benoetigte
Hoehe statt horizontalem Scrollen.
- pipeline.js: Bloecke werden in Dreier-Gruppen aufgeteilt, Direction
ltr/rtl wechselt pro Reihe. Zwischen Reihen rendert ein SVG-U-Turn-Pfeil
(Bogen mit Pfeilkopf) die Verbindung. Daten-Fluss-Animation (is-flowing)
funktioniert sowohl auf Inner-Pfeilen als auch auf U-Turns.
- CSS: .pipeline-row mit flex-direction abhaengig von data-direction.
rtl-Reihen kippen Pfeilkopf und Animation in entgegengesetzte Richtung.
U-Turn-Pfad als SVG mit stroke-dasharray-Animation bei aktivem Fluss.
- Mobile (<900px): Snake aufgeloest, alle Reihen werden vertikal
gestapelt, U-Turns ausgeblendet — bestehende Vertikal-Stilistik bleibt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer Tab "Analysepipeline" zwischen Faktencheck und Quellenuebersicht.
Zeigt 9 Verarbeitungsschritte als n8n-artige Blockkette: Quellen sichten,
Nachrichten sammeln, Doppeltes filtern, Relevanz bewerten, Orte erkennen,
Lagebild verfassen, Fakten pruefen, Qualitaetscheck, Benachrichtigen.
- Backend: refresh_pipeline_steps-Tabelle persistiert pro Refresh+Pass die
Status- und Zahlen-Werte. pipeline_tracker.py kapselt Start/Done/Skip/Error
inkl. WebSocket-Broadcast (Event-Typ pipeline_step). 9 Hooks im Orchestrator
speisen die Anzeige.
- API: GET /api/incidents/{id}/pipeline liefert Definition + letzten Stand
(Zahlen aus letztem Refresh, Multi-Pass-Konsolidierung).
- Frontend: pipeline.js rendert Vollbild-Blockkette mit pulsierendem Glow am
aktiven Block, animierten Pfeilen bei Datenfluss, Haekchen am fertigen Block.
Hover-Tooltip mit Erklaerung in Nutzersprache, Klick oeffnet Detail-Popup.
Bei Research-Lagen leuchtet ein Schleifen-Pfeil pro Mehrfach-Durchlauf auf.
Mini-Variante (nur Icons) im Refresh-Progress-Popup.
- CSS: Light/Dark-Theme-fest, dezenter Circuit-Hintergrund (5% Opacity),
Mobile-vertikale Stapelung unter 900px, prefers-reduced-motion respektiert.
- Uebersprungene Schritte (z.B. Geoparsing ohne neue Artikel) werden
ausgeblendet, brandneue Lagen ohne Refresh zeigen Hinweis.
Tooltips bewusst in normaler Sprache ohne Internas (keine Modellnamen,
keine Toolnamen, keine Phasen-Labels).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problem: Beim Anlegen einer neuen Lage verschwand der Blur-Effekt auf dem
Hintergrund-Inhalt sobald das Browserfenster in der Groesse veraendert wurde.
Zudem blieb der Lagen-Titel im Header offen sichtbar, waehrend der Inhalt
darunter geblurrt war — der wechselnde Titel war also klar lesbar.
Ursache:
- Blur lag auf .tab-panels und parallel auf .tab-panel — zwei verschachtelte
Composite-Layer, die bei jedem Reflow neu berechnet werden.
- transition: filter 0.4s ease auf .tab-panel — bei Resize lief die Transition
oft rueckwaerts oder pausierte, was den Blur visuell verschwinden liess.
- .incident-header-strip lag ausserhalb von .tab-panels und war dadurch nie
geblurrt (Titel/Aktionen/Beschreibung blieben offen sichtbar).
Aenderungen:
- Blur-Anker hochgezogen auf #incident-view (Klasse refresh-blurred), so dass
Header und Tab-Panels gemeinsam unscharf werden.
- Nur noch eine Filter-Ebene (filter: blur(8px)).
- Transition entfernt — Blur soll schlagartig kommen und gehen, kein
lesbarer Zwischenzustand beim Reflow.
- will-change: filter; transform: translateZ(0); — erzwingt einen persistenten
GPU-Composite-Layer, der bei Window-Resize stabil bleibt.
Headless-Tests bestaetigen: alte Variante 89.8% Pixel-Stabilitaet ueber 6
Resize-Zyklen mit Content-Mutation, neue Variante 97.0% (Rest = Content-Diff).
Behebt das Symptom, dass Recherche-Lagen wie staging Lage 6 "Friedrich Merz"
trotz erfolgreichem Refresh leer blieben. Claude lieferte nicht-leere Antworten
(1226-2125 Zeichen), die der bisherige Regex-Parser nicht extrahieren konnte —
die Recherche meldete "0 Artikel" und der Refresh wurde stumm als Erfolg
verbucht.
Aenderungen:
- _parse_response, select_relevant_feeds, extract_dynamic_keywords und
select_relevant_telegram_channels nutzen jetzt json.JSONDecoder.raw_decode
ueber Modul-Helper _extract_json_array/_extract_json_object. Damit werden
auch JSON-Bloecke mit Vor-/Nachtext, Markdown-Fences oder verschachtelten
Objekten zuverlaessig erkannt.
- Bei Parse-Fehlschlag wird jetzt ein gekuerztes Sample der Claude-Antwort
geloggt, damit kuenftige Faelle direkt debuggbar sind.
- Neue ResearcherParseError-Exception unterscheidet "echt 0 Treffer" von
"Antwort kaputt". search() gibt zusaetzlich ein parse_failed-Flag zurueck.
- Orchestrator-Multi-Pass: wenn alle 3 research-Durchlaeufe 0 neue Artikel
ergeben UND mindestens einer am Parser scheiterte, wird der Refresh als
Fehler markiert (statt als stiller Erfolg). Der WebSocket-refresh_error
loest dann die sichtbare UI-Meldung aus.
Adhoc-Lagen sind unveraendert: dort fangen RSS und Telegram die kaputte
Claude-Antwort auf, dafuer ist nur die Diagnose im Log neu.
Banner und Was-ist-neu-Modal nutzen jetzt CSS-Variablen
(--bg-card, --text-primary, --accent etc.) statt fester
Dark-Mode-Farben, damit sie sich automatisch dem Hell-/Dunkelmodus
anpassen. RELEASES.json: alte ae/oe/ue-Schreibweisen auf echte
Umlaute umgestellt + neuer Eintrag fuer diesen Fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vorheriger Fix in selectIncident griff nicht beim handleRefresh-Pfad
(manueller Aktualisieren-Klick), weil dieser direkt UI.showProgress aufruft
ohne selectIncident zu durchlaufen. Damit blieb eine Lage, deren erster
Refresh per Klick angestossen wurde, unblurred.
rAF mit add("blurred") jetzt direkt in _showPopupProgress (components.js),
sobald state.isFirst gesetzt ist. Damit greift der Blur in jedem Pfad, der
durch _showPopupProgress laeuft — selectIncident, handleRefresh,
handleStatusUpdate (WebSocket), Initial-Restore.
Der zentrale rAF in selectIncident ist redundant und wieder entfernt.
Der _willReBlur-Skip von remove("blurred") in selectIncident bleibt
erhalten — verhindert ueberfluessiges remove+add im selben Tick.
cache-bust components.js auf v=20260427a, app.js auf v=20260427c.
Vorheriger Patch hatte den rAF nur im Create-Flow gesetzt. Damit funktionierte
zwar das Anlegen, aber das Auswaehlen einer existierenden Lage, deren erste
Recherche gerade laeuft (oder nach einem manuellen ersten Refresh), blieb
unblurred.
Loesung: rAF mit add("blurred") jetzt zentral in selectIncident, sobald der
Progress-State isFirst=true und nicht minimiert ist. Damit greift der Blur
in allen Pfaden (Anlegen, manueller Refresh, Auswahl einer laufenden Lage,
Wechsel zwischen Faellen, Initial-Load via savedId).
Der zusaetzliche rAF in createIncident von 2ee90a4 ist damit redundant und
wieder entfernt — der zentrale Hook in selectIncident deckt den Fall mit ab.
cache-bust app.js auf v=20260427b.
Im Create-Flow wurde .blurred in selectIncident() erst entfernt und gleich
darauf via _showPopupProgress wieder gesetzt. CSS filter:blur greift in der
Kombination (frischer isFirst-State + selectIncident im selben Tick + viel
vorausgehende DOM-Manipulation durch Modal-Close + renderSidebar) nicht
zuverlaessig im selben Frame — der Fall war bis zum Wegklicken/Zurueckklicken
unblurred.
selectIncident: remove("blurred") wird uebersprungen, wenn der zustaendige
Progress-State isFirst=true und nicht minimiert ist (Blur soll bestehen
bleiben statt remove+add im selben Tick).
createIncident: zusaetzlich requestAnimationFrame mit grid.classList.add im
naechsten Frame, falls der Browser den ersten add-Aufruf in selectIncident
nicht visuell uebernommen hat. Doppeltes Setzen ist idempotent.
dashboard.html: cache-bust app.js auf v=20260427a.
Frueher wurde beim allerersten Treffen mit dem Update-System der lastSeen-
Marker stillschweigend gesetzt, ohne Modal — User sah erst beim ZWEITEN
Update was. Fuer Kunden-Onboarding ist das suboptimal: sie sollen direkt
sehen, dass das System existiert und welche Updates aktuell sind.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Beim ersten Login nach einem Update zeigt der Monitor nun ein Modal mit den
Release-Notes des Updates (aus RELEASES.json). Wenn waehrend einer laufenden
Sitzung ein neues Update live geht, erscheint unten rechts ein Banner mit
einem Aktualisieren-Knopf.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Frontend kann jetzt erkennen, wann eine neue Version live ist, und dem Nutzer
einen passenden Hinweis sowie die Release-Notes anzeigen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verhindert dass Staging und Live versehentlich dieselbe DB nutzen
(Symlink data wurde frueher beim git clone mitgeklont und zeigte
auf das gleiche physische Verzeichnis /home/claude-dev/osint-data).
Staging muss jetzt DB_PATH in der .env explizit setzen.
- ENHANCE_PROMPT_ADHOC und ENHANCE_PROMPT_RESEARCH: Umschreibungen durch
echte Umlaute ersetzt (fuer -> fuer, praezises -> praezises, ...). Behebt
den Widerspruch, dass der Prompt "echte Umlaute verwenden" forderte,
die Anweisung selbst aber ae/oe/ue/ss nutzte.
- call_claude() bekommt neuen timeout-Parameter. None = Fallback auf
CLAUDE_TIMEOUT (1800s), sonst Override in Sekunden. asyncio.wait_for
und die cancel-aware Variante nutzen durchgaengig den effective_timeout.
- Enhance-Endpoint ruft call_claude mit timeout=60 auf (Haiku-Single-Shot,
vorher global 1800s).
- chat.py _call_claude_chat: Timeout von 60s auf 120s erhoeht (Chat-Antworten
koennen etwas laenger dauern, haben aber keinen Anspruch auf 30 Min).
- Neue Exception-Klasse ClaudeCliError(error_type, message) in claude_client.py
mit Kategorien rate_limit / auth_error / timeout / cli_error.
- _classify_cli_error() als geteilter Klassifikator (Keywords fuer Rate-Limit
und Auth-Fehler wie "does not have access", "login again").
- call_claude() erkennt jetzt auch is_error=true im JSON bei returncode=0
(Hauptursache des Ausfalls vom 22.04.: CLI liefert "Your organization
does not have access" mit is_error=true statt Exit-Code).
- Orchestrator: ClaudeCliError mit rate_limit/timeout als transient behandelt
(3 Retries mit Backoff 0s/120s/300s). auth_error/cli_error brechen sofort
ab ohne Retry. Behebt den bestehenden Bug, dass Rate-Limit-Fehler gar nicht
retried wurden.
- routers/incidents.py Enhance-Endpoint: ClaudeCliError wird auf
503 (auth_error) / 429 (rate_limit) gemappt, TimeoutError auf 504.
- routers/chat.py _call_claude_chat(): wirft jetzt ClaudeCliError statt
generischem RuntimeError. Chat-Endpoint mappt auth_error auf 503.
- Frontend: neue ApiError-Klasse in api.js mit status+detail.
generateDescription() in app.js zeigt differenzierte Toasts nach
HTTP-Status (503/429/504/403).
- dashboard.html: Cache-Bust api.js + app.js auf v=20260423a
- Neuer Helper charge_usage_to_tenant() in services/license_service.py:
UPSERT in token_usage_monthly und Credits-Abzug aus licenses.credits_used.
Wiederverwendbar fuer alle Claude-Call-Verursacher.
- Orchestrator: Inline-Buchungslogik (35 Zeilen) durch Helper-Aufruf ersetzt.
- routers/incidents.py POST /enhance-description: require_writable_license
statt get_current_user, db_dependency hinzugefuegt, Credits-Buchung mit
source="enhance" nach jedem Claude-Call.
- routers/chat.py POST /: analog require_writable_license + Credits-Buchung
mit source="chat". _call_claude_chat() gibt jetzt zusaetzlich ClaudeUsage
zurueck.
Abgelaufene/gesperrte Lizenzen koennen damit keine Haiku-Calls mehr ausloesen,
und alle Kosten werden konsistent auf Tenant-Ebene verbucht.
Bisher extrahierte der Generator Bullets direkt aus den neu eingesammelten
Artikeln und mergte sie mit den bestehenden Developments. Das fuehrte zu
zwei wiederkehrenden Problemen:
1. Off-topic Artikel, die den Keyword-Prefilter aber nicht den Topic-Filter
passiert hatten, konnten als Bullet landen (die Kachel bildete dann
Nebenschauplaetze des Weltgeschehens ab statt der Lage).
2. Alte Bullets blieben stehen, auch wenn sie laengst nicht mehr die
'neuesten' Entwicklungen waren — nur sehr ueberholte Eintraege fielen
durch das 8-Bullet-Cap raus.
Neue Logik: Der Generator nimmt das frisch erzeugte Lagebild als autoritative
inhaltliche Grundlage und waehlt daraus Bullets aus, die durch eine aktuelle
belegende Meldung (<~7 Tage) gestuetzt sind. Dadurch:
- Thematisch sauber: Lagebild enthaelt bereits nur relevante Inhalte.
- Echt 'neueste': Alte Hintergrund-Erwaehnungen im Lagebild fallen raus,
weil kein aktueller Artikel sie belegt.
- Klar datiert: Zeitstempel zwingend aus article.published_at der
belegenden Meldung.
- Kompakt: 4-6 Bullets (vorher 8), nach Zeitstempel absteigend.
Kein Merge mit previous_developments mehr — bei jedem Refresh neu generiert
(behebt das Drift-Problem). previous_developments bleibt nur als Fallback,
falls der Generator im Einzelfall 0 Bullets parst.
Beim Create-Flow wurde selectIncident() aufgerufen, BEVOR der Fall
als refreshend markiert wurde. Dadurch entfernte selectIncident den
'.blurred'-Zustand des Tab-Containers und rief _lockActionsIfFirst(false)
auf — der Fallinhalt war zwischen Oeffnen und Eintreffen der ersten
WebSocket-Statusnachricht kurzzeitig klickbar und unblurred.
Jetzt wird der Refresh-Status und ein Initial-State mit isFirst=true
schon VOR selectIncident gesetzt. selectIncident erkennt isRefreshing
und ruft _showPopupProgress + _lockActionsIfFirst(true) mit dem
bestehenden State auf — Blur und Lock greifen sofort.
Bei Research-Multi-Pass (3 Durchlaeufe) und bei Retry-Versuchen wird
pro Pass/Retry ein neuer refresh_log-Eintrag mit frischem started_at
angelegt. /incidents/refreshing gab dadurch beim Reload den spaeteren
started_at zurueck statt des urspruenglichen Session-Starts — der
Frontend-Timer sprang auf 0:00 zurueck.
Orchestrator traegt jetzt _current_task_started_at in-memory, gesetzt
beim Queue-Pickup und geraeumt im finally. /incidents/refreshing liefert
diesen Session-Start fuer den aktuell laufenden Task (Fallback: letzter
refresh_log-Eintrag, falls der Server zwischenzeitlich neu gestartet
wurde).
Neue Artikel passieren jetzt vor DB-Speicherung einen Haiku-Relevanzfilter
(AnalyzerAgent.filter_relevant_articles), der Artikel verwirft, die nur
auf generische Keywords matchen, aber das Kernthema der Lage nicht
inhaltlich behandeln. Bei Parsing-/API-Fehler oder 100%-Rejection: Fallback
auf unveraenderte Kandidatenliste.
Orchestrator trennt DB-Dedup und INSERT, damit der Filter nur auf neue
Kandidaten laeuft (Kostenoptimierung). LATEST_DEVELOPMENTS-Prompt erhaelt
zusaetzliche Relevanz-Gate-Regel als zweite Sicherung.
Hintergrund: Incident 'Russische Militaerblogger' sammelte bisher Iran-,
Nahost- und allgemeine Ukraine-Artikel ein, weil Keyword-Match ab 2 von 8
Begriffen ('iran', 'russland', 'drohne', ...) genuegt. Der semantische
Filter verwirft solche Zufallstreffer.