feat(notify): Desktop-Benachrichtigung für den Ruhezeit-Toast

quiet_mode = "toast" war bisher nur eine Protokollzeile. Jetzt zeigt der
Daemon eine Desktop-Benachrichtigung — über die Bordmittel des Systems,
exec-basiert wie osbuiltin, ohne Bibliothek und ohne CGO: notify-send
(Linux), osascript display notification (macOS), PowerShell Toast
(Windows; Text base64, kein Quoting-Problem). Best effort: fehlt das
Werkzeug, bleibt es bei der Protokollzeile plus Start-Warnung; `talk
doctor` zeigt den Weg unter „Toast". Der Aufruf läuft asynchron, ein
hängender Notification-Daemon blockiert die Queue nie.

Queue: OnToast-Hook, gefeuert bei Toast-Verwurf beim Eintritt und im Worker
(nie unter dem Mutex). Tests: notify-Paket mit fake notify-send im PATH,
Prozessgrenze: Ruhezeit + toast → fake notify-send bekommt Titel und Text,
der Provider keinen Treffer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
m3ta-chironandClaude Fable 5.1 committed 2026-10-24 10:37:46 +02:00
1 parent a9ca32bed9
commit 9e2aa506a6
7 files changed
+279 -6

No files matched your search

+16 -2
View File
@@ -16,6 +16,7 @@ import (
"code.m3ta.dev/m3tam3re/talk/internal/audio"
"code.m3ta.dev/m3tam3re/talk/internal/config"
"code.m3ta.dev/m3tam3re/talk/internal/daemon"
"code.m3ta.dev/m3tam3re/talk/internal/notify"
"code.m3ta.dev/m3tam3re/talk/internal/provider"
"code.m3ta.dev/m3tam3re/talk/internal/queue"
"code.m3ta.dev/m3tam3re/talk/internal/speak"
@@ -117,10 +118,13 @@ func doctorChecks(probe bool) []check {
// 4. Provider-Stufen (+ 5. Gesamt-Verfügbarkeit)
out = append(out, checkProviders(cfg, probe)...)
// 6. Mute
// 6. Benachrichtigungen (Ruhezeit-Toast)
out = append(out, checkNotify(cfg))
// 7. Mute
out = append(out, checkMute())
// 7. Gesetze
// 8. Gesetze
out = append(out, checkLaws(cfg))
return out
@@ -271,6 +275,16 @@ func probeTier(p provider.Provider) string {
}
}
func checkNotify(cfg config.Config) check {
if notify.Available() {
return check{"Toast", statusOK, notify.Describe() + " — für quiet_mode = toast"}
}
if cfg.QuietMode == queue.QuietModeToast {
return check{"Toast", statusWarn, notify.Describe() + " — quiet_mode = toast wird nur protokolliert"}
}
return check{"Toast", statusOK, notify.Describe() + " (nur für quiet_mode = toast relevant)"}
}
func checkMute() check {
dir, err := config.Dir(getenv)
if err != nil {