6.6 KiB
Daily Notification Bugs — Diagnosis (Plugin + App)
Context: Fixes were applied in both the plugin and the app, but "reset doesn't fire" and "notification text defaults to fallback" still occur. This doc summarizes what was checked and what to do next.
What Was Verified
App integration (correct)
-
NativeNotificationService.ts
- Pre-cancel is gated: only iOS calls
cancelDailyReminder()before scheduling (lines 289–305). Android skips it. - Schedules with
id: this.reminderId("daily_timesafari_reminder"), plustime,title,body. - Calls
DailyNotification.scheduleDailyNotification(scheduleOptions)(notscheduleDailyReminder).
- Pre-cancel is gated: only iOS calls
-
AccountViewView.vue
editReminderNotification()only callscancelDailyNotification()when not Android (lines 1303–1305). On Android it only callsscheduleDailyNotification().
So the app is not double-cancelling on Android and is passing the expected options.
Plugin in app’s node_modules (fixed code present)
- node_modules/@timesafari/daily-notification-plugin is at version 1.1.4 and contains:
- NotifyReceiver.kt: DB idempotence is skipped when
skipPendingIntentIdempotence=true(wrapped inif (!skipPendingIntentIdempotence)). - DailyNotificationWorker.java:
preserveStaticReminderread from input, stablescheduleIdfor static reminders, andscheduleExactNotification(..., preserveStaticReminder, ...). - DailyNotificationPlugin.kt:
cancelDailyReminder(call)implemented.
- NotifyReceiver.kt: DB idempotence is skipped when
So the source the app uses (from its dependency) already has the fixes.
Plugin schedule path (correct)
- App calls
scheduleDailyNotification→ plugin’sscheduleDailyNotification(call)→ScheduleHelper.scheduleDailyNotification(...). - That helper calls
NotifyReceiver.cancelNotification(context, scheduleId)thenscheduleExactNotification(..., skipPendingIntentIdempotence = true). - So the “re-set” path does set
skipPendingIntentIdempotence = trueand the DB idempotence skip should apply.
Likely Causes Why Bugs Still Appear
1. Stale Android build / old APK
The Android app compiles the plugin from:
android/capacitor.settings.gradle →
project(':timesafari-daily-notification-plugin').projectDir = new File('../node_modules/@timesafari/daily-notification-plugin/android')
If the app was not fully rebuilt after the plugin in node_modules was updated, the running APK may still contain old plugin code.
Do this:
- In the app repo (
crowd-funder-for-time-pwa):./gradlew clean(or Android Studio → Build → Clean Project)- Build and reinstall the app (e.g. Run on device/emulator).
- Confirm you’re not installing an older APK from somewhere else.
2. Dependency not actually updated after plugin changes
The app depends on:
"@timesafari/daily-notification-plugin": "git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#master"
If the fixes were only made in a different clone (e.g. daily-notification-plugin_test) and never pushed to that gitea master, then:
npm install/npm updatein the app would not pull the fixes.- The app’s
node_moduleswould only have the fixes if they were copied/linked from the fixed repo.
Do this:
- If the fixes live in another clone: either push the fixed plugin to gitea
masterand runnpm update @timesafari/daily-notification-plugin(thennpx cap sync android, then clean build), or point the app at the fixed plugin locally, e.g. in apppackage.json:"@timesafari/daily-notification-plugin": "file:../daily-notification-plugin"
(adjust path to your fixed plugin repo), thennpm install,npx cap sync android, clean build and reinstall.
3. Fallback text from native fetcher (Bug 2 only)
TimeSafariNativeFetcher.java in the app is still a placeholder: it always returns:
- Title:
"TimeSafari Update" - Body:
"Check your starred projects for updates!"
That only affects flows that fetch content (e.g. prefetch or any path that uses the fetcher for display). The static daily reminder path does not use the fetcher for display: title/body come from the schedule Intent and WorkManager input. So if you only use the “daily reminder” (one time + custom title/body), the fetcher placeholder should not be the cause. If you have any flow that relies on fetched content for the text, you’ll see that placeholder until the fetcher is implemented and wired (and optionally token persistence).
Verification Steps (after clean build + reinstall)
-
Reset / “re-set” (Bug 1)
- Set reminder for 2–3 minutes from now.
- Edit and save without changing the time.
- Wait for the time; the notification should fire.
- In logcat, filter by the plugin’s tags and look for:
Skipping DB idempotence (skipPendingIntentIdempotence=true) for scheduleId=...Scheduling next daily alarm: id=daily_timesafari_reminder ...
If you see these, the fixed path is running.
-
Static text on rollover (Bug 2)
- Set a custom title/body, let the notification fire once.
- In logcat look for:
DN|ROLLOVER next=... scheduleId=daily_timesafari_reminder static=true
If you seestatic=trueand the samescheduleId, the next occurrence should keep your custom text.
-
Plugin version at build time
- In the app’s
node_modules/@timesafari/daily-notification-plugin/package.json, confirm"version": "1.1.4"(or the version that includes the fixes). - After that, a clean build ensures that version is what’s in the APK.
- In the app’s
Summary
| Check | Status |
|---|---|
| App gates cancel on Android | OK |
| App calls scheduleDailyNotification with id/title/body | OK |
| Plugin in app node_modules has DB idempotence skip | OK (1.1.4) |
| Plugin in app node_modules has static rollover fix | OK |
| Plugin in app node_modules has cancelDailyReminder | OK |
| Schedule path passes skipPendingIntentIdempotence = true | OK |
See also: doc/plugin-feedback-android-rollover-double-fire-and-user-content.md — when two notifications fire (e.g. one ~3 min early, one on the dot) and neither shows user-set content.
Most likely the app is still running an old Android build. Do a clean build and reinstall, and ensure the plugin dependency in the app really points at the fixed code (gitea master or local path). Then re-test and check logcat for the lines above. If the bugs persist after that, the next step is to capture a full logcat from “edit reminder (same time)” through the next fire and from “first fire” through “next day” to see which path runs.