Notifications
Bonus B-02 — event-triggered local/browser notification on chargeRaised, plus in-app feed and badge.
Kinds
- Local native —
flutter_local_notifications(Android/iOS/macOS), triggered byNotificationsCubit.onChargeCreated(Stream<StoredEvent>). - Browser — Web Notifications API via
dart:js_interop+webpackage; same trigger, requires permission grant. - In-app feed —
/notificationsrendersAppNotification { type, titleKey, bodyKey, chargeId?, createdAt, read }with unread filter and mark-read.
Permission
No background push — foreground only. NotificationsCubit.requestPermission() is called from a user gesture (Maintenance Hub chargeRaised banner or settings). Fallback: in-app banner when permission denied.
Data
Backend seeds notifications.json + emits notification.updated SSE on creation. Flutter subscribes via LiveSyncService → LocalStore.notificationsStream → NotificationsCubit (badge count for KxNavShell "Notifications" badge). Mark read is POST /notifications/{id}/read with expectedStateEpoch (naturally idempotent, no Idempotency-Key).
UX
KxNavShellbadge shows2 unreadwithbadgeSemanticLabel; reading in feed decrements live.- Each notification title/body via ARB
titleKey/bodyKey, never inspector prose (that is never translated).
Tests
test/notifications/* asserts adapter permission, badge decrement, mark-read idempotent replay.
Limitations
- Not FCM/APNs/Web Push background; app must be foreground or recently backgrounded.
flutter_local_notificationspermission on Android 13+ is explicitly requested — denied state is recorded and retried on nextchargeRaised.
See Offline & Live Sync for the SSE pipeline that delivers the trigger.