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.
- Update test app index.html with improved UI refresh timing after force-stop
- Includes immediate + delayed refresh pattern for better recovery detection
- Better error handling for database not ready scenarios
- Increase recovery delay from 1s to 3s (force-stop recovery can take time)
- Add immediate refresh + delayed refresh to catch recovery at different stages
- Better error handling for database not ready yet (shows 'Checking...' instead of error)
- Add logging to indicate when config not found is normal (after uninstall/reinstall)
Previously, after force-stop recovery:
- Configuration check happened too early (1s delay not enough)
- UI showed error immediately if database not ready
- Single refresh might miss recovery completion
The fix:
- Immediate refresh when app becomes visible (catches fast recovery)
- Delayed refresh after 3 seconds (catches slower recovery)
- Better error messages indicating database might not be ready yet
- Note that config not found is normal after uninstall/reinstall (database wiped)
This ensures UI properly refreshes configuration status and notification info
after force-stop recovery completes.
- Use FLAG_NO_CREATE to get existing PendingIntent instead of creating new one
- Call both alarmManager.cancel() AND pendingIntent.cancel() (matches scheduleExactNotification pattern)
- Fixes issue where cancellation failed, causing idempotence check to skip scheduling
Previously, cancelNotification():
- Used FLAG_UPDATE_CURRENT which could create new PendingIntent
- Only called alarmManager.cancel(), not pendingIntent.cancel()
- Result: PendingIntent still existed after cancellation, causing 'duplicate schedule' skip
The fix:
- Use FLAG_NO_CREATE to get existing PendingIntent (don't create if missing)
- Call both alarmManager.cancel() and pendingIntent.cancel() (matches pattern in scheduleExactNotification)
- This ensures proper cancellation so new alarms can be scheduled
This fixes Test 1 failure where alarms weren't appearing after scheduling.
- Update test app index.html with JSON.stringify improvements for better log readability
- This file is synced from www/index.html during build process
- Includes date formatting improvements for debugging UI refresh issues
- Cancel existing alarm for scheduleId before scheduling new one in scheduleDailyNotification
- Add verification logging to cancelNotification to confirm cancellation worked
- Ensures 'one per day' semantics when updating schedule time
Previously, when updating a schedule time:
- cleanupExistingNotificationSchedules() only canceled OTHER schedules (excluded current scheduleId)
- Old alarm for same scheduleId with different trigger time was not canceled
- Result: 2 alarms existed (old + new) violating 'one per day' semantics
The fix:
- In ScheduleHelper.scheduleDailyNotification(), cancel existing alarm for scheduleId BEFORE scheduling new one
- This ensures when updating schedule time, old alarm is canceled first
- Enhanced logging with DNP-CANCEL tag to track cancellation and verify it worked
Test 2 should now pass: updating schedule time will cancel old alarm before scheduling new one.
- Add loadConfigurationStatus() function to check if plugin/fetcher are configured
- Call loadConfigurationStatus() on page load and app resume (visibility change)
- Add logging to getConfig() to track configuration restoration from database
- UI now shows ✅ Configured status after force-stop recovery
Previously, after force-stop recovery:
- Configuration was stored in database but UI didn't check for it
- configStatus and fetcherStatus remained as 'Not configured' even though config existed
- No logging to track when configuration was restored
The fix:
- loadConfigurationStatus() queries database for native_fetcher_config
- Updates UI status indicators (configStatus, fetcherStatus) based on database state
- Called automatically on page load and when app becomes visible
- Android logs 'DNP-CONFIG: Configuration restored from database' when config is loaded
This ensures the 'Ready to test...' UI block shows correct configuration status
after force-stop recovery, matching the actual database state.
- Add visibility change handler to refresh UI when app becomes visible
- Check for recent notifications and show indicator if notification was received
- Update lastKnownNextNotificationTime on app resume
- Auto-enable fire verification in Test 1 if alarm is within 5 minutes
Previously, after force-stop recovery:
- UI only refreshed on page load (which doesn't fire again after force-stop)
- Notification received indicator didn't show if notification fired while app was stopped
- Test 1 didn't verify that restored alarms actually fire
The fix:
- Listen for visibilitychange event to detect app resume
- Refresh all status displays (plugin, permissions, channel) when app becomes visible
- Check for notifications received in last 2 minutes and show indicator
- Wait 1 second after visibility change to allow recovery to complete
- Test 1 now automatically verifies restored alarms fire if within 5 minutes
This ensures the UI stays in sync after force-stop recovery and shows
notification indicators for notifications that fired while the app was stopped.
- Find existing enabled notify schedule and update its nextRunAt
- Fallback to finding schedule by kind='notify' && enabled=true if not found by ID
- This ensures getNotificationStatus() finds the updated schedule, not a stale one
Previously, during rollover we were creating a new schedule with a different ID
(daily_rollover_...), but getNotificationStatus() was finding the original schedule
(with ID like notify_... or daily_notification) which still had the old nextRunAt.
The fix:
- First try to find schedule by the provided stableScheduleId
- If not found, find the existing enabled notify schedule (there should only be one)
- Update that schedule's nextRunAt instead of creating a new one
- This ensures getNotificationStatus() returns the correct nextNotificationTime
This matches the pattern used in getNotificationStatus() which finds schedules
with kind='notify' && enabled=true.
- Change DatabaseSchema.Schedule to Schedule (same package, no prefix needed)
- Fixes compilation error: Unresolved reference: DatabaseSchema
Since both NotifyReceiver.kt and DatabaseSchema.kt are in the same
package (com.timesafari.dailynotification), Schedule can be referenced
directly without the DatabaseSchema prefix.
- Add database schedule update after successfully scheduling alarm
- Update existing schedule's nextRunAt or create new schedule entry
- Extract cron expression and clockTime from trigger time
- This ensures getNotificationStatus() returns correct nextNotificationTime after rollover
Previously, scheduleExactNotification() scheduled the alarm correctly but
didn't update the database schedule's nextRunAt. This caused getNotificationStatus()
to return stale data, making the UI show the old notification time even though
the alarm was correctly rescheduled for tomorrow.
The fix:
- After successfully scheduling the alarm, update or create the schedule in the database
- Set nextRunAt to the triggerAtMillis value
- Extract hour:minute from trigger time to populate cron and clockTime fields
- Use runBlocking to call suspend function from non-suspend context
- Log but don't fail if DB update fails (alarm is already scheduled)
This matches the pattern used in ReactivationManager for boot/app launch recovery.
- Use JSON.stringify() to log actual values instead of [object Object]
- Add ISO date strings for nextNotificationTime and lastNotificationTime
- Add raw timestamp values alongside formatted dates
- Log pending count and other status fields for debugging
This will help diagnose why the UI isn't updating after rollover
by showing the actual values returned from getNotificationStatus().
- Fix date comparison to check alarm date against current date, not initial alarm date
- Remove false warning when alarm date unchanged but scheduled for future (correct behavior)
- Only warn if alarm date is in the past (actual failure case)
- Improve logic flow: date change confirms rollover, but rollover logs are primary indicator
- When dates match but alarm is scheduled for today/future, defer to rollover log check
This fixes the confusing warning message that appeared when:
- Initial alarm scheduled for tomorrow (2025-12-31)
- Notification fires and rollover reschedules for same tomorrow date
- Rollover logs confirm rollover occurred, but date comparison incorrectly warned
The test now correctly recognizes that an alarm scheduled for the future
is valid, and relies on rollover logs as the primary verification method.
- Fix async/await usage in background fetch handler
- Fix Core Data metadata access errors
- Replace SQLITE_TRANSIENT with nil for Swift compatibility
- Fix PermissionStatus interface and type casts in test app
- Add iOS setup documentation to BUILDING.md
- Update iOS sync workflow to handle Podfile regeneration
Resolves all iOS compilation errors and improves test app setup process.
- Add detailed [UI Refresh] prefix logging to loadPluginStatus()
- Log plugin availability checks, status calls, and UI updates
- Add detailed [Poll] prefix logging to checkNotificationDelivery()
- Log status check results, notification delivery detection, and time comparisons
- Log rollover detection with old/new timestamp details
- Log periodic refresh triggers and initialization of tracking variables
- Include structured object logging for debugging UI refresh behavior
This enables debugging of UI auto-refresh mechanism and visibility
into JavaScript console logs in captured logcat output during test runs.
- Add detailed logging to loadPluginStatus() with [UI Refresh] prefix
- Add detailed logging to checkNotificationDelivery() with [Poll] prefix
- Log status check results, change detection, and refresh triggers
- Log nextNotificationTime comparisons to debug rollover detection
- Include Capacitor/Console in logcat capture pattern to capture JS logs
- Log notification delivery detection and time calculations
- Log when rollover is detected and UI refresh is triggered
This enables debugging of UI auto-refresh mechanism and visibility
into JavaScript console logs in captured logcat output.
- Add TestEventsPlugin for receiving ADB broadcast intents
- Create operator console UI (console/index.html, console.css, console.js)
- Add test plan structure (plan.json) with phases, tests, and steps
- Wire all test scripts (phase1, phase2, phase3) with step context and events
- Add event emission helpers to alarm-test-lib.sh (step_start, step_pass, etc.)
- Update test-phase1.sh with comprehensive prerequisite verification
- Register TestEventsPlugin in capacitor.plugins.json
- Add console documentation (CONSOLE-USAGE.md, CONSOLE-REMAINING-WORK.md)
- Add test implementation alignment tracking (TEST-IMPLEMENTATION-ALIGNMENT.md)
This enables real-time test progress tracking via structured events
from shell scripts to the operator console UI.
- Normalize alarm time seconds to :00 for consistent comparison
- Compare dates (YYYY-MM-DD) instead of full timestamps to detect rollover
- Expand logcat search patterns to catch all rollover logs (DN|RESCHEDULE, etc.)
- Add 5-second wait after notification fire to allow rollover processing
- UI: Normalize seconds display to :00 in all time displays
- UI: Add auto-refresh mechanism that detects nextNotificationTime changes
- UI: Poll every 3 seconds and force refresh when rollover detected
- UI: Initialize tracking variable on page load for change detection
Fixes issue where test passed but alarm time didn't actually change,
and UI wasn't updating to show rescheduled notification time after rollover.
The test app UI expects 'notificationsEnabled' and 'exactAlarmEnabled'
fields from checkPermissionStatus(), but the plugin only returned
technical field names ('postNotificationsGranted', 'exactAlarmGranted').
Added compatibility fields:
- notificationsEnabled = postNotificationsGranted && notificationsEnabledAtOsLevel
- exactAlarmEnabled = exactAlarmGranted
This ensures the UI can correctly display permission status after
granting permissions.
Implement P0-P5 directives for operator clarity, consistent outcomes, and
easy evidence capture across all test phases.
Changes:
- alarm-test-lib.sh: Add evidence collection (capture_alarms, capture_logcat,
capture_screenshot), verdict functions (verdict_pass/warn/fail), run directory
management, and release gating support (RELEASE_GATE_PHASE3)
- test-phase1.sh: Refactor to unified framework with CLI modes (--setup,
--run, --smoke, --all, --ci), micro-prompts, evidence capture, and verdict
blocks for all 5 tests
- test-phase2.sh: Add evidence capture, verdict blocks, and STRICTNESS policy
(soft/hard) for warn vs fail behavior
- test-phase3.sh: Add evidence capture, verdict blocks, release gating
(--gate-phase3), and fatigue reduction (time estimates, automation hints)
- RUNBOOK-TESTING.md: New comprehensive operator guide (669 lines) covering
prerequisites, all phases, evidence locations, verdict interpretation,
common failures, and troubleshooting
All test scripts now use consistent UI helpers (section, substep, info, ok,
warn, error), standardized evidence collection, and clear verdict reporting.
Evidence is saved to timestamped run directories (runs/<RUN_ID>/) with alarms,
logs, and screenshots organized by test phase and scenario.
Tests pass with consistent presentation and reproducible evidence collection.
Fix configuration error 'No value for persistToken' by using optBoolean()
instead of getBoolean(). The getBoolean() method throws JSONException
when the key doesn't exist, while optBoolean() returns the default value
(false) safely.
This allows configureNativeFetcher() to work when persistToken is not
provided in the options, which is the expected default behavior (tokens
are not persisted by default for security).
Section 3.1 fails due to Capacitor plugin architecture constraint, not missing Android SDK.
Clarification:
- Error: 'Capacitor Android project not found'
- Reason: Capacitor plugins cannot be built standalone
- Expected: This is normal behavior for Capacitor plugins
- Solution: Build from test-app or integrated Capacitor app
This is an architectural constraint, not a code issue. The runbook should note that Android build verification requires a Capacitor app context.
Update progress documents to reflect production readiness work completion.
Changes:
- 00-STATUS.md: Added PHASE 16 (Production Readiness) to phase table
- 01-CHANGELOG-WORK.md: Added production readiness section with runbook and TODO scan details
- Updated last updated dates to reflect completion
Production Readiness Status:
- Runbook: Complete with full mechanical checklist
- TODO Scan: Enhanced with core/docs split (0 core TODOs)
- Verification: All key anchors verified
- Status: Ready for production readiness verification
Exclude false positive TODOs from scan results:
- todo-scan.js script's own markers (in comments/strings)
- Documentation comments that mention TODO intentionally
This ensures core code count accurately reflects production code TODOs.
Verification:
- Core code count now shows actual production TODOs only
- Script's own markers excluded
- Documentation comments excluded