# Consuming app handoff: iOS native fetcher + chained dual schedule

This document is for the **host app** repository (e.g. crowd-funder-for-time-pwa) after bumping `@timesafari/daily-notification-plugin` to a version that includes:

- **iOS** `NativeNotificationContentFetcher`–style registration (`DailyNotificationPlugin.registerNativeFetcher`)
- **iOS** `updateStarredPlans` / `getStarredPlans` (parity with Android `daily_notification_timesafari` / `starredPlanIds` semantics)
- **iOS** chained dual flow: user notification is **armed only after** prefetch completes (delay if fetch is late; max slip 15 minutes before fallback copy)
- **Android** chained dual flow: exact **notify** alarm is scheduled **after** dual prefetch completes (no longer scheduled at initial `scheduleDualNotification` before fetch)

Material from `doc/new-activity-notifications-ios-android-parity.md` still applies; this file adds **app-side** steps not spelled out there.

---

## 1. iOS — register native fetcher before `configureNativeFetcher`

The plugin now **rejects** `configureNativeFetcher` if no fetcher is registered (aligned with Android).

**In `AppDelegate` (or earliest app startup before Capacitor calls into the plugin):**

```swift
import CapacitorDailyNotification // actual product module name may match the Pod (e.g. CapacitorDailyNotification)

// After: import DailyNotificationPlugin if your target uses a different module name — use the same module that exposes DailyNotificationPlugin.

DailyNotificationPlugin.registerNativeFetcher(TimeSafariNativeFetcher.shared)
```

Implement **`TimeSafariNativeFetcher`** as a Swift type that:

- Conforms to `NativeNotificationContentFetcher`
- Implements `fetchContent(context: FetchContext) async throws -> [NotificationContent]` with the same **Endorser** behavior as `TimeSafariNativeFetcher.java` (`POST …/api/v2/report/plansLastUpdatedBetween`, starred plan IDs, JWT pool selection, aggregation copy, pagination / `last_acked_jwt_id` as in Java)
- Implements `configure(apiBaseUrl:activeDid:jwtToken:jwtTokenPool:)` if the fetcher needs credentials pushed from TypeScript (optional; TS still persists `native_fetcher_config` UserDefaults key)

**Starred plan IDs for the fetcher:** Read JSON array string from UserDefaults key **`daily_notification_timesafari.starredPlanIds`** (written by `updateStarredPlans` from JS). Format matches Android: JSON array of strings.

---

## 2. iOS — `UNUserNotificationCenterDelegate` / rollover

Chained dual notifications set:

- `notification_id` = `org.timesafari.dailynotification.dual` (same stable identifier as before)
- `scheduled_time` = `NSNumber` (fire time in ms)

Ensure your existing `DailyNotificationDelivered` bridge still forwards **`notification_id`** and **`scheduled_time`** from **notification content `userInfo`** (not only from a custom payload). Foreground presentation handlers should read `notification.request.content.userInfo`.

---

## 3. Android — no API change for `setNativeFetcher`

Host apps that already call `DailyNotificationPlugin.setNativeFetcher(TimeSafariNativeFetcher(...))` and `configureNativeFetcher` from JS keep that flow.

**Behavior change:** the dual **notify** alarm is no longer scheduled at the initial `scheduleDualNotification` call; it is scheduled when **dual prefetch work finishes** (success or hard failure path), at `max(nextNotifyAt, now)` so late prefetch delays the notification.

---

## 4. Bump and sync

1. Bump **`@timesafari/daily-notification-plugin`** in the app `package.json`.
2. `npm install`
3. `npx cap sync ios && npx cap sync android`
4. iOS: `cd ios/App && pod install` (adjust path if your app uses a different `ios` layout)
5. Clean build in Xcode / Android Studio

---

## 5. QA focus

- **iOS:** Register fetcher **before** any `configureNativeFetcher` from the web layer; confirm `updateStarredPlans` is no longer `UNIMPLEMENTED`.
- **Both:** New Activity dual path: first notification should appear **after** prefetch for that cycle, not at a fixed time with stale API text.
- **Android:** Regression-test `cancelDualSchedule` and Daily Reminder (should remain independent).

---

## 6. Assumptions

- Swift host implements `TimeSafariNativeFetcher`; the plugin does **not** embed `plansLastUpdatedBetween` on iOS when a host fetcher is registered (mirrors Android).
- Module import name for the Capacitor iOS plugin follows your Pod (`CapacitorDailyNotification` in `CapacitorDailyNotification.podspec`).
