feat: Implement Android native plugin with offline-first pipeline
- Add DailyNotificationPlugin main class with Capacitor integration - Implement NotificationContent model following project directive schema - Create DailyNotificationStorage with tiered storage approach - Add DailyNotificationScheduler with exact/inexact alarm support - Implement DailyNotificationFetcher for background content retrieval - Create DailyNotificationReceiver for alarm handling - Add WorkManager workers for background tasks and maintenance - Implement prefetch → cache → schedule → display pipeline - Add comprehensive error handling and logging - Support battery optimization and adaptive scheduling
This commit is contained in:
242
.cursor/rules/project.mdc
Normal file
242
.cursor/rules/project.mdc
Normal file
@@ -0,0 +1,242 @@
|
||||
# TimeSafari Notifications — LLM Implementation Directive (v2.0)
|
||||
_Last updated: August 12, 2025_
|
||||
|
||||
## 0) Role & Objective
|
||||
**You are the implementation LLM.** Build an **offline-first daily notifications system** for Android (Kotlin) and iOS (Swift) that **prefetches -> caches -> schedules -> displays** content **without requiring network at display time**. Favor **reliability over richness**.
|
||||
|
||||
## 1) Golden Rules
|
||||
1. **Follow the pipeline:** **Prefetch → Cache → Schedule → Display.**
|
||||
2. **Never depend on network at display time.** All assets must be local.
|
||||
3. **Design for failure.** Always have a last-known-good and an emergency fallback.
|
||||
4. **Keep content scannable (<3s), single message, actionable.**
|
||||
5. **Measure everything** (fetch success, delivery, engagement, stale usage).
|
||||
6. **Minimize battery impact** and respect platform limitations and user settings.
|
||||
7. **Ask only when needed:** if a required input is missing, use the defaults below; otherwise proceed.
|
||||
|
||||
## 2) Default Assumptions (use unless overridden)
|
||||
- **Product mode:** Lightweight Daily Updates (text + emoji) with option to extend to media later.
|
||||
- **Fetch size:** 1–2 KB JSON daily.
|
||||
- **User schedule default:** 07:30 local time, daily.
|
||||
- **Quiet hours:** None (app-level quiet hours supported but disabled by default).
|
||||
- **Analytics:** Local log + pluggable uploader (no-op by default).
|
||||
|
||||
## 3) Deliverables
|
||||
Produce the following artifacts:
|
||||
|
||||
### Android (Kotlin)
|
||||
- `:core`: models, storage, metrics, fallback manager.
|
||||
- `:data`: fetchers (WorkManager), mappers, cache policy.
|
||||
- `:notify`: scheduler (AlarmManager), receiver, channels.
|
||||
- App manifest entries & permissions.
|
||||
- Unit tests for fallback, scheduling, metrics.
|
||||
- README with battery optimization instructions (OEMs).
|
||||
|
||||
### iOS (Swift)
|
||||
- `NotificationKit`: models, storage, metrics, fallback manager.
|
||||
- BGTaskScheduler registration + handler.
|
||||
- UNUserNotificationCenter scheduling + categories + attachments.
|
||||
- Unit tests for fallback, scheduling, metrics.
|
||||
- README with Background App Refresh caveats + Focus/Summary notes.
|
||||
|
||||
## 4) Permissions & Required Setup
|
||||
### Android Manifest
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission/SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
```
|
||||
- Create a high-importance **NotificationChannel** `timesafari.daily`.
|
||||
- If **SCHEDULE_EXACT_ALARM** denied on Android 12+, auto-fallback to inexact.
|
||||
|
||||
### iOS App Setup (AppDelegate / SceneDelegate)
|
||||
- Register `BGTaskScheduler` with ID `com.timesafari.daily-fetch`.
|
||||
- Request alerts, sound, badge via `UNUserNotificationCenter`.
|
||||
- Create category `DAILY_UPDATE` with a primary `View` action.
|
||||
- Ensure Background Modes: **Background fetch**, **Remote notifications** (optional for future push).
|
||||
|
||||
## 5) Data Model (keep minimal, versioned)
|
||||
### Canonical Schema (language-agnostic)
|
||||
```
|
||||
NotificationContent v1
|
||||
- id: string (uuid)
|
||||
- title: string
|
||||
- body: string (plain text; may include simple emoji)
|
||||
- scheduledTime: epoch millis (client-local target)
|
||||
- mediaUrl: string? (for future; must be mirrored to local path before use)
|
||||
- fetchTime: epoch millis
|
||||
```
|
||||
### Kotlin
|
||||
```kotlin
|
||||
@Entity
|
||||
data class NotificationContent(
|
||||
@PrimaryKey val id: String,
|
||||
val title: String,
|
||||
val body: String,
|
||||
val scheduledTime: Long,
|
||||
val mediaUrl: String?,
|
||||
val fetchTime: Long
|
||||
)
|
||||
```
|
||||
### Swift
|
||||
```swift
|
||||
struct NotificationContent: Codable {
|
||||
let id: String
|
||||
let title: String
|
||||
let body: String
|
||||
let scheduledTime: TimeInterval
|
||||
let mediaUrl: String?
|
||||
let fetchTime: TimeInterval
|
||||
}
|
||||
```
|
||||
|
||||
## 6) Storage Layers
|
||||
**Tier 1: Key-Value (quick)** — next payload, last fetch timestamp, user prefs.
|
||||
**Tier 2: DB (structured)** — history, media metadata, analytics events.
|
||||
**Tier 3: Files (large assets)** — images/audio; LRU cache & quotas.
|
||||
|
||||
- Android: SharedPreferences/DataStore + Room + `context.cacheDir/notifications/`
|
||||
- iOS: UserDefaults + Core Data/SQLite + `Library/Caches/notifications/`
|
||||
|
||||
## 7) Background Execution
|
||||
### Android — WorkManager
|
||||
- Periodic daily work with constraints (CONNECTED network).
|
||||
- Total time budget ~10m; use **timeouts** (e.g., fetch ≤30s, overall ≤8m).
|
||||
- On exception/timeout: **schedule from cache**; then `Result.success()` or `Result.retry()` per policy.
|
||||
|
||||
### iOS — BGTaskScheduler
|
||||
- `BGAppRefreshTask` with aggressive time budgeting (10–30s typical).
|
||||
- Submit next request immediately at start of handler.
|
||||
- Set `expirationHandler` first; cancel tasks cleanly; **fallback to cache** on failure.
|
||||
|
||||
## 8) Scheduling & Display
|
||||
### Android
|
||||
- Prefer `AlarmManager.setExactAndAllowWhileIdle()` if permitted; else inexact.
|
||||
- Receiver builds notification using **BigTextStyle** for long bodies.
|
||||
- Limit actions to ≤3; default: `View` (foreground intent).
|
||||
|
||||
### iOS
|
||||
- `UNCalendarNotificationTrigger` repeating at preferred time.
|
||||
- Category `DAILY_UPDATE` with `View` action.
|
||||
- Media attachments **only if local**; otherwise skip gracefully.
|
||||
|
||||
## 9) Fallback Hierarchy (must implement)
|
||||
1. **Foreground prefetch path** if app is open.
|
||||
2. **Background fetch** with short network timeout.
|
||||
3. **Last good cache** (annotate staleness: “as of X”).
|
||||
4. **Emergency phrases** (rotate from static list).
|
||||
|
||||
Provide helper:
|
||||
- `withStaleMarker(content) -> content'` appends age label (e.g., “from 3h ago”).
|
||||
|
||||
## 10) Failure Matrix & Responses
|
||||
| Scenario | Detect | Action |
|
||||
|---|---|---|
|
||||
| No network / timeout | Exceptions / status | Use last-good; schedule |
|
||||
| Invalid JSON | Parse error | Use emergency content; log |
|
||||
| Storage full | Write error | Evict old; retry minimal payload |
|
||||
| Notifications disabled | OS state | In-app education screen |
|
||||
| Background killed | Gaps in execution | Catch-up next foreground open |
|
||||
|
||||
## 11) Metrics (local first; uploader optional)
|
||||
Track per attempt:
|
||||
```
|
||||
NotificationMetrics v1
|
||||
- scheduledTime, actualDeliveryTime?
|
||||
- contentAge (ms)
|
||||
- engagement: {TAPPED, DISMISSED, IGNORED}?
|
||||
- failureReason?
|
||||
- platformInfo (oem, os version, app state)
|
||||
```
|
||||
- Compute: **Fetch Success Rate**, **Delivery Rate**, **Engagement Rate**, **Stale Content Rate**.
|
||||
|
||||
## 12) Testing Requirements
|
||||
### Matrix (minimum)
|
||||
- Android 12+ foreground/background/killed; with/without Battery Saver; Wi‑Fi/Mobile/Offline.
|
||||
- iOS 16+ background/Low Power/Focus/Scheduled Summary on & off.
|
||||
- Offline at trigger time (must still display).
|
||||
|
||||
### Unit Tests (examples)
|
||||
- Fallback when fetch fails (uses last-good and marks stale).
|
||||
- Exact vs inexact scheduling path selected correctly.
|
||||
- Metrics recorded for each stage.
|
||||
|
||||
## 13) UX Standards
|
||||
- One clear message; no clutter.
|
||||
- ≤2 actions; primary takes user into app.
|
||||
- Respect quiet hours if configured.
|
||||
- Provide onboarding: value explanation → permission request → time picker → test notification → tips for OEM battery settings (Android) or Focus/Summary (iOS).
|
||||
|
||||
## 14) Code Stubs (must generate & wire)
|
||||
### Android — Worker (core pattern)
|
||||
```kotlin
|
||||
class DailyContentWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
|
||||
override suspend fun doWork(): Result = try {
|
||||
withTimeout(8.minutes) {
|
||||
val content = fetchDailyContent(timeout = 30.seconds)
|
||||
saveToCache(content)
|
||||
scheduleNotification(content)
|
||||
}
|
||||
Result.success()
|
||||
} catch (e: TimeoutCancellationException) {
|
||||
scheduleFromCache(); Result.success()
|
||||
} catch (e: Exception) {
|
||||
scheduleFromCache(); Result.retry()
|
||||
}
|
||||
}
|
||||
```
|
||||
### iOS — BG Refresh Handler (core pattern)
|
||||
```swift
|
||||
func handleBackgroundRefresh(_ task: BGAppRefreshTask) {
|
||||
scheduleNextRefresh()
|
||||
var finished = false
|
||||
task.expirationHandler = { if !finished { cancelNetwork(); task.setTaskCompleted(success: false) } }
|
||||
fetchDailyContent(timeout: 15) { result in
|
||||
defer { finished = true; task.setTaskCompleted(success: result.isSuccess) }
|
||||
switch result {
|
||||
case .success(let content): quickSave(content); scheduleNotification(content)
|
||||
case .failure: scheduleFromCache()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 15) Security & Privacy
|
||||
- Use HTTPS; pin if required.
|
||||
- Strip PII from payloads; keep content generic by default.
|
||||
- Store only what is necessary; apply cache quotas; purge on logout/uninstall.
|
||||
- Respect OS privacy settings (Focus, Scheduled Summary, Quiet Hours).
|
||||
|
||||
## 16) Troubleshooting Playbook (LLM should generate helpers)
|
||||
- Android: verify permission, channel, OEM battery settings; `adb shell dumpsys notification`.
|
||||
- iOS: check authorization, Background App Refresh, Low Power, Focus/Summary state.
|
||||
|
||||
## 17) Roadmap Flags (implement behind switches)
|
||||
- `FEATURE_MEDIA_ATTACHMENTS` (default off).
|
||||
- `FEATURE_PERSONALIZATION_ENGINE` (time/frequency, content types).
|
||||
- `FEATURE_PUSH_REALTIME` (server-driven for urgent alerts).
|
||||
|
||||
## 18) Definition of Done
|
||||
- Notifications deliver daily at user-selected time **without network**.
|
||||
- Graceful fallback chain proven by tests.
|
||||
- Metrics recorded locally; viewable log.
|
||||
- Clear onboarding and self-diagnostic screen.
|
||||
- Battery/OS constraints documented; user education available.
|
||||
|
||||
## 19) Quick Start (LLM execution order)
|
||||
1. Scaffold modules (Android + iOS).
|
||||
2. Implement models + storage + fallback content.
|
||||
3. Implement schedulers (AlarmManager / UNCalendarNotificationTrigger).
|
||||
4. Implement background fetchers (WorkManager / BGTaskScheduler).
|
||||
5. Wire onboarding + test notification.
|
||||
6. Add metrics logging.
|
||||
7. Ship minimal, then iterate.
|
||||
|
||||
---
|
||||
|
||||
### Appendix A — Emergency Fallback Lines
|
||||
- "🌅 Good morning! Ready to make today amazing?"
|
||||
- "💪 Every small step forward counts. You've got this!"
|
||||
- "🎯 Focus on what you can control today."
|
||||
- "✨ Your potential is limitless. Keep growing!"
|
||||
- "🌟 Progress over perfection, always."
|
||||
Reference in New Issue
Block a user