Incident-Response: sources_json nur noch via Lazy-Endpunkt, Sidebar schlank

Backend:
- IncidentResponse: sources_json-Feld entfernt (Detail-GET liefert es
  nicht mehr mit).
- Neues Schema IncidentListItem fuer GET /incidents (Sidebar):
  Ohne summary, ohne sources_json. Ein has_summary-Bit fuer
  Erster-Refresh-Erkennung, description bleibt fuer das Edit-Modal.
- list_incidents selektiert nur die noetigen Spalten (kein SELECT *)
  — spart bei grossen Lagen Speicher + Serialisierung.
- Neuer Endpunkt GET /incidents/{id}/sources liefert geparstes
  Sources-Array fuer Zitate-Lookups (Lazy).

Frontend:
- api.js: getIncidentSources(id).
- app.js: loadIncidentDetail laedt /sources parallel, speichert Array
  in _currentSources. Alle renderSummary/Zusammenfassung/
  LatestDevelopments-Aufrufe bekommen jetzt _currentSources statt
  incident.sources_json. inc.summary-Checks -> inc.has_summary.
- components.js: _parseSources(input) akzeptiert Array ODER String
  (Rueckwaertskompatibilitaet). renderZusammenfassung, renderSummary,
  renderLatestDevelopments nutzen den Helper.

Hintergrund: Die Sidebar-Liste lieferte bei 17 Lagen 1,23 MB
(Iran allein 386 KB wegen sources_json + summary). Detail-Endpunkt
lieferte sources_json (324 KB bei Iran) bei jedem Oeffnen mit.
Beides jetzt radikal kleiner — die 324 KB Sources gibt's nur
einmalig auf Anfrage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
2026-04-20 00:07:46 +02:00
Ursprung a302790777
Commit 0d6ad8ea90
5 geänderte Dateien mit 118 neuen und 22 gelöschten Zeilen

Datei anzeigen

@@ -709,13 +709,27 @@ const UI = {
return { zusammenfassung, remaining: remaining.trim() };
},
/**
* Parst sources: akzeptiert Array (neu, vom /sources-Endpunkt) ODER
* JSON-String (alt, aus sources_json) fuer Rueckwaertskompatibilitaet.
*/
_parseSources(input) {
if (!input) return [];
if (Array.isArray(input)) return input;
try {
const parsed = JSON.parse(input);
return Array.isArray(parsed) ? parsed : [];
} catch (e) {
return [];
}
},
/**
* Rendert die Zusammenfassung als HTML (Bullet Points).
*/
renderZusammenfassung(text, sourcesJson) {
if (!text) return '<span style="color:var(--text-disabled);">Noch keine Zusammenfassung.</span>';
let sources = [];
try { sources = JSON.parse(sourcesJson || '[]'); } catch(e) {}
const sources = this._parseSources(sourcesJson);
// Nur Bullet-Point-Zeilen behalten, Fliesstext herausfiltern
const bulletLines = text.split("\n").filter(line => line.trim().startsWith("- "));
const bulletText = bulletLines.length > 0 ? bulletLines.join("\n") : text;
@@ -751,8 +765,7 @@ const UI = {
*/
renderLatestDevelopments(text, sourcesJson) {
if (!text) return '<span style="color:var(--text-disabled);">Noch keine Entwicklungen erfasst.</span>';
let sources = [];
try { sources = JSON.parse(sourcesJson || '[]'); } catch(e) {}
const sources = this._parseSources(sourcesJson);
const bulletLines = text.split("\n").map(l => l.trim()).filter(l => l && (l.startsWith("- ") || l.startsWith("[")));
if (bulletLines.length === 0) {
@@ -869,8 +882,7 @@ const UI = {
renderSummary(summary, sourcesJson, incidentType) {
if (!summary) return '<span style="color:var(--text-tertiary);">Noch keine Zusammenfassung.</span>';
let sources = [];
try { sources = JSON.parse(sourcesJson || '[]'); } catch(e) {}
const sources = this._parseSources(sourcesJson);
// Markdown-Rendering
let html = this.escape(summary);