feat(notruf): Wiederauslöse-Sperre + grüner Hinweis; "Neues Gespräch" in die Kopfzeile

1) Notruf-Sperre (Cooldown) gegen Mehrfach-Alarme verwirrter/verängstigter
   Senioren:
   - Neue Einstellung emergency_cooldown_minutes (Default 5), zur Laufzeit im
     Admin-Bereich änderbar (RUNTIME_SETTABLE), 0 = aus.
   - record_manual_emergency unterdrückt innerhalb der Sperre jeden weiteren
     Alarm (kein zweiter Versand, kein Log-Eintrag) und liefert status=cooldown
     mit minutes_ago/remaining_seconds. Neuer Endpunkt GET /api/emergency/status
     für die Anzeige nach einem Seiten-Neuladen. Store: last_manual_emergency_at.
   - UI: Nach dem Alarm wird der Button grün, zeigt „✓" und pulsiert für die
     Dauer der Sperre. Erneuter Druck öffnet einen grünen Hinweis-Dialog:
     „Ich habe Deine Helfer vor <n> Minuten alarmiert. Wahrscheinlich ist Deine
     Hilfe schon unterwegs." Der grüne Zustand übersteht ein Neuladen
     (Status-Abruf beim Start).

2) „Neues Gespräch" als Symbol (Stift) zurück in die Kopfzeile — zwischen
   Sprachauswahl und Menü; löst dieselbe Aktion aus wie der Menüeintrag.

Tests: neue test_emergency_cooldown.py (Status/Suppression/Endpoint); zwei
bestehende Notruf-Tests an die neue Interface-Methode bzw. die Sperre
angepasst. 250 Offline-Tests grün. Frontend via Headless-Chrome verifiziert
(Alarm->grün/pulsierend, Hinweis-Dialog, Reload-Persistenz, Header-Button).
Bump app.js v=50 -> v=51.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-26 15:58:17 +02:00
commit 8c152ee77c
10 changed files with 290 additions and 22 deletions

View file

@ -243,6 +243,10 @@
<option value="ru">🇷🇺 RU</option>
<option value="zh">🇨🇳 ZH</option>
</select>
<button id="new-chat-top" type="button" title="Neues Gespräch" aria-label="Neues Gespräch"
class="shrink-0 h-9 w-9 grid place-items-center rounded-lg border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
</button>
<button id="menu-toggle" title="Menü" aria-label="Menü"
class="h-9 w-9 grid place-items-center rounded-lg border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-700 text-xl leading-none transition-colors">⋮</button>
</header>
@ -386,6 +390,24 @@
</div>
</div>
<script src="/app.js?v=50"></script>
<!-- ═══════════════ Notruf bereits ausgelöst (Sperre) ═══════════════ -->
<div id="sos-cooldown" role="dialog" aria-modal="true" aria-labelledby="sos-cooldown-title"
class="hidden fixed inset-0 z-[60] flex items-center justify-center bg-black/50 p-4">
<div class="w-full max-w-sm rounded-2xl bg-white dark:bg-slate-800 shadow-xl overflow-hidden">
<div class="bg-green-600 text-white px-5 py-4 flex items-center gap-3">
<span class="text-3xl font-extrabold"></span>
<span id="sos-cooldown-title" class="text-lg font-semibold">Hilfe ist unterwegs</span>
</div>
<div class="px-5 py-4">
<p id="sos-cooldown-text" class="text-base text-slate-700 dark:text-slate-200"></p>
</div>
<div class="px-5 pb-5">
<button type="button" id="sos-cooldown-ok"
class="w-full h-14 rounded-xl bg-green-600 hover:bg-green-700 text-white text-xl font-extrabold transition-colors">Verstanden</button>
</div>
</div>
</div>
<script src="/app.js?v=51"></script>
</body>
</html>