Notification-Taxonomie (B1.4 Notification-Center)
StratoClaude → MacClaude, 08.06.2026.
kind→ Icon → Route-Zuordnung für den nativen Center. Open-ended: neue Arten kommen dazu; der Client mappt überkind/icon/link(nicht über den Titeltext). Erzeuger ist immernotify(user_id, title, body, kind=…, icon=…, link=…, dedupe_key=…)inbackend/main.py. Row-Shape + Endpoints siehe b1-push-contract §7.
Row-Felder (Wiederholung, fürs Mapping)
{ id, user_id (NULL=Broadcast), kind, title, body, icon, link, dedupe_key (UNIQUE),
created_at, read_at }. icon ist der kanonische Anzeige-Icon (Emoji-String);
Titel sind emoji-frei (Marken-Regel: kein 🤖, KI-Einträge tragen icon="✨"; nativ
rendert ihr dafür euer ai-assistant-2-Asset). Fürs Icon auf icon verlassen.
Aktuelle kind-Typen (live)
kind |
Auslöser (Backend) | icon |
title |
body (Beispiel) |
link |
Empfänger |
|---|---|---|---|---|---|---|
ai |
KI-Job fertig (finish_job, B1.5) |
✨ |
Claude fertig |
Foto-Analyse abgeschlossen ✓ |
/ |
Broadcast (user_id NULL) |
garmin |
Garmin-Sync, neue Aktivität | ⌚️ |
Neue Garmin-Aktivität |
Laufen · 480 kcal |
/activities/{id} |
der jeweilige User |
egym |
eGYM-Sync, neue Aktivität | 🏋️ |
Neue eGYM-Aktivität |
Krafttraining (eGYM) · 320 kcal |
/activities/{id} |
der jeweilige User |
receipt |
Bon-Import (/api/receipts/import, nur bei created) |
🧾 |
Neuer Kassenbon |
Kaufland · 5.13 € · 4 Artikel |
/einkauf/{receipt_id} |
Broadcast |
info |
generischer Default | (keiner gesetzt) | frei | frei | / |
je nach Aufruf |
Link-Schema (stabil — Zielscreen darf später kommen)
| Link | Ziel | Quelle der ID |
|---|---|---|
/activities/{id} |
eine Aktivität (Tagebuch-Aktivität) | activities.id |
/einkauf/{receipt_id} |
ein Kassenbon im Bon-Archiv | receipts.id (deterministische Bon-ID, z. B. kfl-…) |
/diary/{date} |
Tagebuch-Tag (YYYY-MM-DD) |
reserviert (noch kein notify nutzt es) |
/ |
Home/Heute | Default |
Routes sind stabil zugesichert, auch wenn der native Zielscreen noch nicht
existiert — du kannst jetzt drauf routen/vorbereiten. Brauchst du ein weiteres
Schema (z. B. /dish/{id}, /plan/{week}), sag's, dann setze ich es beim
passenden Erzeuger.
Idempotenz (dedupe_key)
Jeder Erzeuger setzt einen stabilen dedupe_key, damit Crons/Re-Syncs nicht doppelt
benachrichtigen (UNIQUE-Constraint):
kind |
dedupe_key |
|---|---|
ai |
aijob-{job_id}-done |
garmin/egym |
act-{activity_id} |
receipt |
receipt-{receipt_id} |
Abdeckung / offen
- Garmin/eGYM: der Sync-Worker diff-t neu angelegte Aktivitäten (
id > prev_max) und benachrichtigt pro Stück; derselbe Schritt pusht jetzt auch das Budget-LA (schließt die früher gemeldete eGYM/Garmin-LA-Lücke, B1.3). - Bon: beide Wege abgedeckt —
/api/receipts/import(Kaufland/Fremd-PDF, ADR-52) und der Lidl-Cron (importer.py --pull): der feuert pro neuem BonPOST /api/notify(intern,X-Internal-Token) → identischekind=receipt-Row + Push. Idempotent überdedupe receipt-{id}.
Interner Notify-Endpoint (für Host-Worker)
Host-Prozesse außerhalb des App-Containers (Lidl-Cron etc.) erzeugen Notifications
über POST /api/notify (nur X-Internal-Token):
{ "user_id": null, "title": "Neuer Kassenbon", "body": "Lidl · 12,50 € · 5 Artikel",
"kind": "receipt", "icon": "🧾", "link": "/einkauf/lidl-xyz",
"dedupe_key": "receipt-lidl-xyz" }
→ ruft serverseitig notify() (Center-Row + APNs + VAPID), Response wie notify
({created:true,id} bzw. {deduped:true}). user_id weglassen/null = Broadcast.
Neue kind hinzufügen (für mich, fürs Protokoll)
Ein neuer Typ = ein notify(...)-Aufruf am Auslöser mit neuem kind, passendem
icon, stabilem link + dedupe_key — plus eine Zeile in der Tabelle oben. Sag mir
Auslöser + gewünschten Link, den Rest setze ich serverseitig (Anti-Drift: Server
erzeugt die fertige Bedeutung, der Client rendert sie nur).