Do not enqueue DailyNotificationFetchWorker for static reminder schedules.
Display is already handled by the single NotifyReceiver alarm; prefetch was
using fallback content and scheduling a second alarm via legacy
DailyNotificationScheduler, causing two notifications at fire time.
Remove existingPendingIntent.cancel() in the "cancel existing alarm before
rescheduling" block. The cached PendingIntent can be the same instance passed
to setAlarmClock; cancelling it can prevent the new alarm from firing. Keep
only alarmManager.cancel(existingPendingIntent) to clear the previous alarm.
NotifyReceiver's post-schedule DB update no longer uses the "first enabled
notify schedule" fallback when stableScheduleId starts with "daily_rollover_".
That fallback was updating the app's schedule row (e.g. daily_timesafari_reminder)
with the rollover time and could leave the app's next alarm in a bad state after
a notification fired.
Add docs/CONSUMING_APP_ANDROID_NOTES.md with notes for consuming apps: debounce
double scheduleDailyNotification calls, and include DailyNotificationReceiver
in logcat when debugging alarms that are scheduled but do not fire.
Fixes two integration bugs with the consuming app (Time Safari) and adds
Android parity for cancel-by-id.
Problem:
- Re-setting a daily notification (edit/save same time) could cancel the
alarm then skip re-scheduling because DB idempotence still ran and
treated the update as a duplicate.
- After the first fire, rollover scheduled the next run with
isStaticReminder=false, so title/body reverted to fallback.
- App calls cancelDailyReminder({ reminderId }) but Android had no
implementation (only cancelAllNotifications and scheduleDailyReminder).
Changes:
- NotifyReceiver.kt: Run DB idempotence only when
!skipPendingIntentIdempotence. When true (e.g. app reset flow), skip
the check and log; prevents "no alarm" after cancel-then-schedule.
- DailyNotificationWorker.java: In scheduleNextNotification(), read
is_static_reminder from WorkManager input; keep stable scheduleId for
static reminders; pass preserveStaticReminder and reminderId into
scheduleExactNotification(); add DN|ROLLOVER log.
- DailyNotificationPlugin.kt: Add cancelDailyReminder(call) that parses
reminderId (or id, reminder_id, scheduleId), calls
NotifyReceiver.cancelNotification(context, scheduleId), and does
best-effort DB cleanup (setEnabled false, updateRunTimes null).
Files modified:
- android/.../NotifyReceiver.kt
- android/.../DailyNotificationWorker.java
- android/.../DailyNotificationPlugin.kt
Cancel-then-schedule was skipped because the idempotence check still
found the cancelled PendingIntent in Android's cache. Skip
PendingIntent idempotence on the cancel-then-schedule path so the
new schedule is always set.
- NotifyReceiver.scheduleExactNotification: add
skipPendingIntentIdempotence (used only from scheduleDailyNotification)
- ScheduleHelper: pass skipPendingIntentIdempotence=true after
cancelNotification(scheduleId)
- Version 1.1.2: package.json, CHANGELOG, README, TS/Android refs
- docs/CONSUMING_APP_OPTIONAL_ANDROID_ID_CLEANUP.md: optional app
cleanup to use one stable id on both platforms
Covers USB debugging setup, battery optimization settings for major OEMs
(Samsung, Xiaomi, OnePlus, Huawei, Oppo), log monitoring, and
troubleshooting. Complements EMULATOR_GUIDE for real-device validation.
Set Intent.setPackage(context.packageName) when creating PendingIntents
for AlarmManager so the broadcast is delivered to DailyNotificationReceiver
on all OEMs. Alarms were firing but the receiver was not invoked when the
component was not explicitly package-targeted.
- NotifyReceiver: setPackage on schedule, cancel, and isAlarmScheduled intents
- ReactivationManager: alarmsExist() use DailyNotificationReceiver + setPackage
- DailyNotificationScheduler: setPackage on ExactAlarmManager path intent
EMULATOR_GUIDE.md:
- Add "Checking and Installing Prerequisites" (how to check Node, npm, Java,
ANDROID_HOME, adb, emulator, AVDs; install steps; reference to
scripts/check-environment.js)
- Use API 35 and Pixel8_API35 throughout to match project compileSdk/targetSdk
- Document arm64-v8a for Apple Silicon and x86_64 for Intel; add
troubleshooting for "x86_64 not supported on aarch64 host"
- Bump version to 1.1.0 and last-updated date
test-apps/daily-notification-test/scripts/build.sh:
- When building only Android (--android / --run-android), run
cap:sync:android instead of cap:sync so iOS pod install is skipped and
Android build/run succeeds without fixing the iOS Podfile
- Add "Checking and Installing Prerequisites" section:
- How to check Node, npm, Java, ANDROID_HOME, adb, emulator, AVDs
- Reference scripts/check-environment.js for partial check
- Install steps for Node, Java, Android SDK (cmdline-tools only),
sdkmanager packages, and avdmanager AVD creation
- Align SDK and AVD with project: use API 35 (android-35, build-tools 35.0.0,
Pixel8_API35) to match compileSdk/targetSdk in variables.gradle
- Bump guide version to 1.1.0 and last-updated date
Version bump reflects new features merged from ios-2 branch:
- iOS rollover recovery for background/inactive app scenarios
- Build script improvements and iOS support
- Test app enhancements and UI improvements
This is a MINOR version bump per semantic versioning due to
backward-compatible feature additions.
Updates BUILDING.md to reflect recent changes in build-native.sh, especially
the Xcode Command Line Tools prerequisite check and the clean-build script.
Problem:
- BUILDING.md didn't mention Xcode Command Line Tools prerequisite
(recently added to build-native.sh)
- clean-build.sh script exists but wasn't documented
- iOS build troubleshooting lacked Command Line Tools guidance
Changes:
- Add Xcode Command Line Tools to Prerequisites section
- Document installation command (xcode-select --install)
- Include verification steps (xcode-select -p, xcodebuild -version)
- Note that build script automatically checks for these tools
- Explain that sqlite3 is part of Command Line Tools
- Document clean-build.sh script in Build Scripts section
- Basic usage: ./scripts/clean-build.sh
- All options: --all, --clean-gradle-cache, --clean-derived-data,
--reinstall-node
- Explain when to use clean builds
- Enhance iOS Native Build Process section
- Add prerequisite note about Command Line Tools
- Include troubleshooting commands for pod install issues
- Reference prerequisites section for details
- Add comprehensive troubleshooting sections
- Clean Build section at start of Troubleshooting
- Recommends clean-build as first step for many issues
- Lists when to use clean builds
- iOS Build Issues section
- Command Line Tools configuration errors
- SQLite/linker issues and pkgx conflicts
- CocoaPods installation problems
- All with clear solutions and commands
The documentation now accurately reflects:
- Xcode Command Line Tools as required iOS prerequisite
- clean-build.sh as available build tool
- Complete iOS troubleshooting workflow
Files modified:
- BUILDING.md
The cap:sync:ios npm script was failing with "pod: command not found"
because npm scripts don't inherit the same PATH environment as shell
scripts, preventing detection of pod installed via rbenv shims.
Added pod-install.sh wrapper script that:
- Detects pod via command -v or rbenv shims ($HOME/.rbenv/shims/pod)
- Executes pod install with the found command
- Matches the pod detection logic used in build-native.sh
Updated cap:sync:ios script to use the wrapper instead of calling
pod install directly, ensuring consistent pod detection across
all build contexts.
Prevents iOS build failures caused by pkgx SQLite linking conflicts and
ensures Xcode Command Line Tools are properly installed.
Problem:
- pkgx installs SQLite built for macOS, causing linker errors when building
for iOS simulator: "linking in dylib built for 'macOS'"
- Missing Command Line Tools cause build failures without clear error messages
Changes:
- Add check_sqlite_conflicts() function
- Detects pkgx SQLite installations in ~/.pkgx
- Warns about macOS dylibs that will cause iOS simulator build failures
- Checks for system SQLite from Command Line Tools
- Validates library paths (DYLD_LIBRARY_PATH, LD_LIBRARY_PATH)
- Add check_command_line_tools() function
- Verifies Xcode Command Line Tools are installed and configured
- Checks for xcodebuild availability
- Verifies sqlite3 is available (part of Command Line Tools)
- Provides clear error messages with installation instructions
- Enhance pkgx detection in iOS build functions
- Specifically searches for pkgx SQLite dylibs
- Automatically removes pkgx paths from PATH environment variable
- Provides detailed warnings about detected conflicts
- Cleans all problematic environment variables before building
- Integrate checks into environment validation
- Runs automatically when building for iOS
- Provides early warnings before build starts
- Fails fast with clear error messages if tools are missing
This fixes the linker error:
"ld: building for 'iOS-simulator', but linking in dylib
(/Users/trent/.pkgx/sqlite.org/v3.44.2/lib/libsqlite3.0.dylib)
built for 'macOS'"
The build script now:
- Detects pkgx SQLite conflicts before building
- Automatically fixes environment variables
- Verifies Command Line Tools are installed
- Provides clear guidance for manual fixes if needed
Files modified:
- scripts/build-native.sh
Fixed Android test app build failures by ensuring Capacitor is synced
before building and automatically fixing missing file references.
Changes:
- Add Capacitor Android sync step before building test app
- Runs `npm run cap:sync:android` to create required project structure
- Ensures `:capacitor-cordova-android-plugins` project exists before Gradle resolves dependencies
- Add automatic fix for missing cordova.variables.gradle reference
- Detects when capacitor.build.gradle references non-existent file
- Automatically comments out problematic `apply from` line
- Uses platform-agnostic sed command (handles macOS and Linux)
- Provides clear logging about what was fixed
- Reorganize build flow to match iOS pattern
- Sync Capacitor first, then navigate to platform directory
- Apply fixes, then build
This fixes the build error:
"Could not resolve project :capacitor-cordova-android-plugins"
"No matching variant of project :capacitor-cordova-android-plugins was found"
The build now completes successfully even when:
- Capacitor hasn't been synced yet
- capacitor.build.gradle references missing files
Files modified:
- scripts/build-native.sh
Fixed build failures when test app dependencies aren't installed and when
Capacitor files don't exist during initial install.
Changes:
- Use `npx run-p` in test app build script to work without local install
- Add dependency check in build-native.sh before building Vue app
- Make fix-capacitor-plugins.js resilient to missing files during postinstall
- Gracefully handles missing capacitor.plugins.json on first install
- Provides clear messaging about when fixes will be applied
- No longer exits with error when Capacitor hasn't been synced yet
This allows the build to complete successfully even when:
- npm dependencies aren't installed in test app
- Capacitor files don't exist yet (will be created during cap:sync:ios)
Files modified:
- scripts/build-native.sh
- test-apps/daily-notification-test/package.json
- test-apps/daily-notification-test/scripts/fix-capacitor-plugins.js
Fixes iOS build failures caused by linker picking up macOS SQLite
libraries from pkgx instead of iOS system SQLite, resulting in
undefined symbol errors for all sqlite3 functions.
Changes:
- Explicitly link system SQLite library (-lsqlite3) in podspec
- Detect and unset pkgx environment variables during iOS builds
- Add warnings to guide users if manual intervention needed
The issue occurs when pkgx (or similar package managers) set
DYLD_LIBRARY_PATH or PKGX_DIR, causing the linker to find macOS
SQLite dylibs at /Users/*/.pkgx/sqlite.org/*/lib/libsqlite3.0.dylib
instead of the iOS system SQLite library.
This fix ensures the iOS build always uses the correct system SQLite
library regardless of environment variable interference.
Adds a comprehensive clean script that removes all build artifacts,
caches, and dependencies to help reproduce build issues across
different development environments.
The script cleans:
- TypeScript build output (dist/)
- iOS plugin artifacts (Pods, Podfile.lock, build dirs)
- Android plugin artifacts (build dirs, optional .gradle cache)
- Test app artifacts (node_modules, dist/, iOS/Android builds)
- Optional: Xcode DerivedData, Gradle cache, node_modules reinstall
Usage:
./scripts/clean-build.sh # Basic clean
./scripts/clean-build.sh --all # Full clean with reinstall
This is particularly useful when troubleshooting build failures
that may be environment-specific (different Xcode, CocoaPods,
macOS, or Node versions).
Implement enhanced app launch recovery to detect and schedule missed rollover
notifications that occurred while the app was terminated, backgrounded, or
inactive.
Key improvements:
- Detect missed rollovers on app launch by checking for past notifications
without next scheduled notification
- Add active rollover check when app becomes active (handles inactive app
scenario where notifications fire silently)
- Calculate forward to future time when next scheduled time is in the past
(handles delays > rollover interval)
- Enhance duplicate detection to exclude original notification from checks
- Retry rollover if previous attempt failed (rollover time set but no next
notification exists)
Changes:
- DailyNotificationReactivationManager: Add detectAndProcessMissedRollovers()
method and performActiveRolloverCheck() for app becoming active
- DailyNotificationReactivationManager: Enhance warm start scenario to check
for missed rollovers
- DailyNotificationScheduler: Add forward calculation loop when next scheduled
time is in the past
- DailyNotificationPlugin: Register observer for UIApplication.didBecomeActiveNotification
to trigger rollover check when app becomes active
Fixes rollover scheduling for:
- App terminated: Rollover now detected and scheduled on next launch
- App inactive/backgrounded: Rollover detected when app becomes active
- Delayed recovery: Handles cases where app reopened after rollover interval
has passed by calculating forward to next future time
All scenarios now properly schedule rollover notifications regardless of app
state when notification fires.
Add inline comments and documentation explaining how to temporarily
change rollover notification intervals from 24 hours to 2 minutes
for testing purposes. Comments specify exact line numbers and values
to change, making it easy to switch between production and testing
modes without losing context.
Changes:
- Add TESTING section to calculateNextScheduledTime() documentation
- Add inline TESTING comments at three change points:
* Calendar date addition (24 hours → 2 minutes)
* Fallback time calculation (24 hours → 2 minutes)
* Duplicate prevention threshold (1 hour → 1 minute)
All code remains at production settings (24-hour intervals).
The pending field in NotificationStatus is a boolean, but the UI
was displaying it directly, causing "true" to appear instead of
a numeric count when notifications were scheduled.
Added a pendingCount computed property that converts boolean
values to numbers (true → 1, false → 0) while also handling
number types for future compatibility.
Save notification content to storage immediately after scheduling
rollover notification so it can be retrieved when the notification
fires. Without this, processRollover fails to find the content
and cannot schedule the next notification.
The rollover flow creates a new notification with a new ID
(daily_rollover_*) but was only scheduling it with the system,
not saving it to storage. When the notification fired, the
lookup by ID failed because the content wasn't stored.
This matches the pattern used in DailyNotificationScheduleHelper
which saves content before scheduling.
Created standardized StatusList component to eliminate duplicate status
display code across views. Standardized styling:
- Flexbox layout with space-between justification
- Border-bottom dividers (removed via :last-child)
- Optional status-based color coding via show-status-colors prop
- Consistent padding (12px 0) and spacing
Migrated HomeView and StatusView to use the new component:
- HomeView: Replaced inline status list, removed ~50 lines of duplicate CSS
- StatusView: Replaced diagnostics info items, removed ~25 lines of duplicate CSS
- Removed unused helper functions (getStatusType, getStatusDescription)
- Fixed TypeScript type assertions for status values
- Added diagnosticsItems computed property in StatusView
Reduces code duplication by ~75 lines and provides single source of truth
for status list styling across the application.
Replaced StatusCard components with simpler inline list layout:
- Removed card bounding boxes and side padding
- Added horizontal dividers between status items
- Title and value on same line using grid layout (left/right justified)
- Reduced padding and margins for more compact display
- Removed unused StatusCard import
Fixed compilation error where userInfo was referenced outside its scope
in handleNotificationDelivery error logging. Changed to use
notification.userInfo directly.
Add comprehensive logging to trace rollover notification handling
from AppDelegate delivery through to next notification scheduling.
This enables diagnosis of why rollover notifications fail to
schedule the next 24-hour notification.
Changes:
- Log observer registration on plugin load
- Log handler entry and data extraction in handleNotificationDelivery
- Log processing steps in processRollover including:
* Missing scheduler/storage detection
* Content lookup failures with available IDs
* ScheduleNextNotification success/failure
These logs will help identify whether the issue is:
- Observer not receiving notifications
- Content missing from storage
- Scheduling logic failing silently
Replace hardcoded '09:00' default with dynamic calculation that sets
the notification time to 3 minutes from now (rounded up to next minute).
This makes it more convenient for users to quickly test notifications
without manually adjusting the time field.
The "Time Until" field in NotificationsView was not updating when
refresh was clicked or over time because the computed property used
Date.now() directly, which is evaluated once and doesn't trigger
reactive updates.
Changes:
- Add reactive currentTime property that updates every second
- Set up interval in mounted() to keep countdown live
- Clean up interval in beforeUnmount() to prevent memory leaks
- Update timeUntilNext computed to use reactive currentTime
- Update refreshNotifications() to immediately refresh currentTime
The countdown now updates automatically every second and immediately
when the refresh button is clicked, without requiring navigation away
and back to the view.
The AppDelegate's willPresent method was not posting the
DailyNotificationDelivered notification event that the plugin
observes to trigger rollover scheduling. This caused rollover
notifications (scheduled 24 hours after the current notification)
to never be created, even though the rollover logic was fully
implemented in the plugin.
The fix extracts notification_id and scheduled_time from the
notification's userInfo and posts them via NotificationCenter using
the decoupled pattern. This allows the plugin to detect notification
delivery and automatically schedule the next day's notification.
Rollover now works correctly: when a notification is delivered,
the plugin schedules the next notification for 24 hours later,
and the NotificationsView properly displays the next notification
timestamp.
Add platform-specific emojis (🤖 Android, 🍎 iOS, 🌐 Web) and status
indicators (✅ Ready, ⚠️ Not Ready, ❓ Unknown) to improve visual
clarity in the home view welcome section.
Replace placeholder content with functional notification status viewer that
displays scheduled notifications and rollover information. Enables verification
of both manually scheduled notifications and automatic rollover scheduling
(24-hour recurrence).
Features:
- Display next scheduled notification time with formatted date/time
- Show time until next notification (days, hours, minutes)
- Display pending notification count
- Show last notification delivery time
- Display rollover status (enabled/disabled, last rollover time) when available
- Additional status info (enabled, scheduled, errors)
- Manual refresh button for status updates
- Loading and error states with platform detection
Uses typed plugin wrapper for type safety with fallback to raw plugin access
for rollover fields not in TypeScript interface (iOS-specific fields).
Replace hardcoded platform values and appStore.platform with
Capacitor.getPlatform() for accurate runtime platform detection.
Changes:
- HomeView: Use Capacitor.getPlatform() instead of appStore.platform
- StatusView: Use Capacitor.getPlatform() for initial diagnostics
- diagnostics-export: Replace hardcoded 'Android' with Capacitor detection
- StatusView: Fix timezone to use actual timezone instead of 'Unknown'
- diagnostics-export: Show 'N/A' for API Level on iOS/web (Android-specific)
Fixes platform badge showing "web" on iOS native and diagnostics
showing incorrect platform information.
The Raw Diagnostics pre element was overflowing its container and
causing the entire page to require horizontal scrolling, making field
values in the diagnostics info section inaccessible.
Added min-width: 0 and overflow: hidden to .diagnostics-json container
to allow proper grid constraint propagation. Added max-width: 100%,
width: 100%, and box-sizing: border-box to .json-output to ensure
it respects container bounds while maintaining horizontal scroll
within the pre element itself.
Add testAlarm() method to iOS plugin for quick notification testing.
Fix plugin method discovery by registering testAlarm in CAPBridgedPlugin
pluginMethods array. Add force-load code in AppDelegate to ensure plugin
is discovered by Capacitor's objc_getClassList scan.
Changes:
- Add testAlarm() implementation in DailyNotificationPlugin.swift
- Register testAlarm in pluginMethods array (required for Capacitor discovery)
- Add force-load code in test app AppDelegate (matches working ios-test-app)
- Add UNUserNotificationCenterDelegate to show notifications in foreground
- Add test notification button to ScheduleView with immediate feedback
- Add debug logging for method discovery and plugin loading
Fixes issue where testAlarm was implemented but returned "UNIMPLEMENTED"
because it wasn't registered in the pluginMethods array. Also ensures
plugin class is loaded before Capacitor's discovery phase.
getExactAlarmStatus() is an Android-only API and was causing
UNIMPLEMENTED errors on iOS. Now only calls the method on Android
platforms, with safe defaults for iOS.
- Check platform before calling getExactAlarmStatus()
- Use default values { enabled: false, supported: false } on iOS
- Add error handling for Android call failures
- Make exact alarm status logging conditional on Android
Create a new RequestPermissionsView that provides a dedicated interface
for checking and requesting notification permissions, matching the
functionality found in the iOS test app.
The view includes:
- Status display with color-coded states (requesting/granted/error)
- Permission status grid showing notifications, exact alarm, and
background refresh status
- Request Permissions button with same functionality as iOS test app
- Platform-specific settings access buttons
- Automatic status refresh on mount
Updated HomeView to navigate to the new view instead of calling
permission request function directly, providing better UX with
dedicated screen for permission management.
- Add back buttons to all sub-views (Schedule, Status, Notifications, History, Logs, Settings, UserZero, About)
- Fix router navigation by importing router instance directly (resolves TypeScript errors with vue-facing-decorator)
- Update back button styling: flex layout with page title, arrow-only label
- Fix "Check Status" action to navigate to StatusView instead of checking status inline
- Remove horizontal padding on mobile views (max-width 768px) for edge-to-edge layout
- Simplify badge styling in HomeView (remove padding and border-radius)
Adds iOS platform support to the unified build script, enabling
building of test-apps/daily-notification-test for iOS alongside
existing Android support.
Changes:
- Add build_plugin_for_test_app_ios() to build iOS test app
- Add build_ios() function for iOS platform handling
- Make environment checks conditional based on target platform
- Add get_pod_command() helper to handle CocoaPods via rbenv
- Update main() to accept --platform ios and include iOS in "all"
This aligns the script with BUILDING.md documentation (lines 71, 75)
which implied iOS support was already available. The iOS build
process mirrors Android: creates plugin symlink, builds Vue app,
syncs Capacitor iOS (handles Podfile fixes), and builds with
xcodebuild for simulator.
Platform-specific environment checks allow iOS-only builds without
requiring Android toolchain, and vice versa.
Fixed two build issues preventing Android plugin compilation:
1. Build script now builds from test app context instead of standalone
- Capacitor Android is only available as a project dependency, not from Maven
- Plugin must be built within a Capacitor app's Android project
- Changed build_plugin_for_test_app() to build from test app's android/
directory where Capacitor is available as :capacitor-android project
2. Added JVM arguments for Java 17+ KAPT compatibility
- Java 21's module system blocks KAPT from accessing internal compiler classes
- Added --add-opens flags to both org.gradle.jvmargs and kotlin.daemon.jvmargs
- Kotlin compiler daemon runs separately and needs its own configuration
- Applied to both plugin and test app gradle.properties files
These changes allow the plugin to build successfully with Java 21 and ensure
it's built in the correct context where Capacitor dependencies are available.
Add inline style resets to html, body, and #app elements to
eliminate browser default margins and padding. This ensures consistent layout
baseline across browsers and complements the centralized padding
management in App.vue.
Remove individual padding declarations from view components and
set padding to 0 on App.vue root container. This consolidates
padding management in one place for easier maintenance and
consistent spacing control.