If randomUUID is unavailable (older WebViews), generate a one-time ID
with Date.now + random segment, log a single DeviceId warning, and
persist it as before so registration still works.
Add getOrCreateDeviceId() backed by Capacitor Preferences so one UUID
survives app restarts and token refreshes. Include deviceId in POST
/notifications/register alongside fcmToken, platform, and testMode.
Add @capacitor/preferences and lightweight DeviceId logs (no token/ID values).
Expose wall-clock fire targets from the iOS NotificationInspector
(scheduled_time userInfo and predictive_<epochMs> ids) so the debug
panel is not misleading when nextTriggerDate resamples for interval
triggers. Extend TS types and show the scheduled target in the UI,
with a note when iOS nextTriggerDate diverges.
Make refreshPending a plain fetch so mock refresh, wakeup ping, flood
test, and clear notifications can refresh the pending list while an
outer withBusy guard is already active.
Add includeDevToolkitRoutes (vite dev or MODE !== production) and use it
from the router, AccountViewView, and NotificationDebugView so the debug
screen matches dev-notifications registration after vite build.
Update the gated banner copy to refer to production Vite builds.
Register the dev-notifications route whenever the bundle is non-production
(DEV or Vite MODE !== production), matching the account screen so RouterLink
to Notification Debug does not throw after vite build.
Align AccountViewView isDev with that rule and document the coupling.
Add NotificationInspectorPlugin.swift to the App target compile sources so
AppDelegate can register the plugin.
Vue’s template compiler treats bindings as non-module JS, so
`import.meta.env.DEV` in `v-if` broke the Capacitor/Vite build.
Expose a readonly `isDev` from the script instead.
- Report UNIMPLEMENTED from Android NotificationInspector instead of empty pending
- Surface iOS-only inspector message in NotificationDebugPanel without noisy errors
- Gate Account debug link with import.meta.env.DEV and document intent
- Add architecture comments on NotificationDebugService, inspector plugin, and native exports
Add a dev-only Notification Debug Panel at /dev/notifications for testing
predictive refresh and WAKEUP_PING without a backend.
- Gate route and Advanced Settings entry on import.meta.env.DEV
- NotificationDebugService drives mock refresh, flood test, clear, and
wake simulation via existing handleCapacitorPushNotificationReceived and
applyNotificationRefreshPayload (shared with refreshNotifications)
- Add NotificationInspector Capacitor plugin: iOS lists pending
UNNotificationRequest identifiers and next trigger; Android stub returns
empty pending for safe registration
Stop converting backend timestamps to HH:mm/recurring schedules and remove
createSchedule/updateSchedule reconciliation. After a successful refresh payload,
clear existing notifications and schedule exact timestamps via the plugin
scheduleNotifications API (with back-compat clear fallback) to prevent drift.
Trigger refreshNotifications on composable mount and document resume, using a
debounced/in-flight guarded wrapper to avoid rapid duplicate refresh calls.
Expose the debounced refresh function from useNotifications.
Cancel all native notifications before applying the backend-provided schedule so
refreshNotifications always performs a full replacement and never leaves stale
entries behind.
Update refreshNotifications to POST /notifications/refresh and map returned
nextNotifications timestamps to clockTime schedules, upserting them via the
DailyNotification schedule APIs (with deterministic IDs) after refreshing native
fetcher credentials.
Add refreshNotifications (configureNativeFetcherIfReady) and
handleCapacitorPushNotificationReceived for data.type WAKEUP_PING; invoke from
Capacitor pushNotificationReceived without UI.
Add registerToken POST to /notifications/register (platform, testMode).
Call it from Capacitor registration and Firebase getToken with deduped
registerRetrievedToken; expose registerToken via barrel and useNotifications
as registerFcmToken.
Add firebaseMessagingClient to ensure the Firebase app is created from VITE_FIREBASE_*,
wire PushNotifications (listeners, requestPermissions, register) before token work,
and call getMessaging/getToken/onMessage when firebase/messaging is supported. Hook
startup from main.capacitor and set PushNotifications presentationOptions in
capacitor.config. Depend on firebase and @capacitor/push-notifications.
Restructure the quick start with Web, Android, and iOS subheadings; put
each npm command in its own code block; fold the test-page step into the
Web section. Document Android (build:android:test:run + ADB, link to
BUILDING.md) and iOS (build:ios:studio + Xcode prerequisites).
Use generic/platform=iOS Simulator instead of a fixed device name so CLI builds
do not fail when that simulator is not installed (e.g. newer Xcode runtimes).
Pass -quiet to xcodebuild and enable SWIFT_SUPPRESS_WARNINGS plus
GCC_WARN_INHIBIT_ALL_WARNINGS for scripted builds and IPA archive/export so
terminal output stays smaller; full diagnostics remain available in Xcode.
Add TimeSafariNativeFetcher (plansLastUpdatedBetween parity with Android) and
call DailyNotificationPlugin.registerNativeFetcher from AppDelegate before JS
configureNativeFetcher; broaden DailyNotificationDelivered scheduled_time types
in willPresent. Wire the new file into the App target; normalize PBX object IDs
to 24-char hex.
Document plugin ≥3 handoff (consuming-app-handoff-ios-native-fetcher-chained-dual),
refresh iOS/Android parity and notification-from-api-call file tables.
Add doc/new-activity-notifications-ios-android-parity.md covering dual-schedule
and Endorser API parity, plugin vs app work, Android dual-path notes, prefetch
vs notify ordering on iOS (§3.3), and clarified Phase B JWT pool status on
both platforms. Link the guide from doc/notification-from-api-call.md under the
iOS checklist.
Aggregate API rows into one notification with Starred Project Update(s) titles,
plan names in typographic quotes, and "+ N more have been updated." for multiples.
Stop emitting the empty-data "No Project Updates" fallback. Sync internal docs.
Add syncStarredPlansToNativePlugin() and call it from AccountViewView
(schedule + initializeState) and ProjectViewView.toggleStar when New
Activity is enabled so Android prefetch uses the current starred list.
Update notification-from-api-call.md with the helper and file references.
- Mint BACKGROUND_JWT_POOL_SIZE (90 + 10) distinct background JWTs with
unique jti; pass jwtTokens from nativeFetcherConfig into configureNativeFetcher.
- Android TimeSafariNativeFetcher: overload configure with jwtTokenPool;
select bearer via epoch day mod pool size; fall back to primary jwtToken.
- Log truncated plansLastUpdatedBetween response at DEBUG for prefetch debugging.
Add BACKGROUND_JWT_EXPIRY_DAYS/SECONDS and accessTokenForBackgroundNotifications
via createEndorserJwtForDid; configureNativeFetcher uses it instead of getHeaders
so WorkManager prefetch is not stuck with a 60s access token. Interactive API
calls unchanged.
Add plan-background-jwt-pool-and-expiry.md (Phase A/B, expiryDays + buffer sizing,
pool size 100). Add plugin-feedback-daily-notification-configureNativeFetcher-jwt-pool.md
for daily-notification-plugin: optional jwtTokens on configureNativeFetcher. Link plan
to plugin doc and endorser-jwt-background-prefetch-options.md.
Call configureNativeFetcherIfReady when the app becomes active so getHeaders can
supply a new JWT before the next background prefetch when users return from
background.
In TimeSafariNativeFetcher, read HttpURLConnection#getErrorStream for non-200
responses and log a capped body snippet (including on retryable errors) to
diagnose JWT_VERIFY_FAILED and other API failures.
`npx cap run android` runs `sync` by default, which regenerated
`capacitor.plugins.json` and removed SafeArea and SharedImage entries
after `restore-local-plugins.js` had already run. Use `--no-sync` in
`build-android.sh` (auto-run) and `auto-run.sh` so the launch step does
not overwrite the restored plugin list.
Document how the daily-notification-plugin dual path uses FetchWorker mock/URL
fetch instead of NativeNotificationContentFetcher, schedules fetch immediately
rather than at contentFetch cron, and why DualScheduleHelper shows useCache=false.
Includes acceptance criteria and file pointers for maintainers fixing the plugin.
Log configure-time starred plan count, fetchContent entry (trigger, scheduledTime,
thread), worker start, POST summary (plan count, truncated afterId), and HTTP
status at INFO so logcat shows clearly when the native fetcher runs versus
plugin-only DNP-FETCH paths.
Add an Android-focused procedure for verifying API-driven copy when a
starred plan has updates via plansLastUpdatedBetween, including expected
notification text, prefetch timing, repeatability, and logcat. Clarify
that iOS parity is documented separately and that the native fetcher uses
Account API Server URL (test Endorser is valid), not the Partner API URL.
- Treat updateStarredPlans as optional: catch UNIMPLEMENTED and continue to
scheduleDualNotification so missing native method no longer blocks scheduling.
- Show specific toast when BGTaskSchedulerErrorDomain error 1 occurs (e.g.
Simulator): explain that a real device and Background App Refresh are required.
- Add PluginHeaders diagnostic in AccountViewView and main.capacitor.ts to debug
UNIMPLEMENTED (log DNP methods at call time and at launch).
- Fix main.capacitor.ts build: use CapacitorWindow type and safe cap assignment
so vite build --mode capacitor succeeds.
- Docs: add UNIMPLEMENTED troubleshooting and updateStarredPlans note in
plugin-feedback-ios-scheduleDualNotification.md; add section 8.3 in
notification-new-activity-lay-of-the-land.md.
- Lockfile updates (package-lock.json, Podfile.lock).
Add plugin-feedback-ios-scheduleDualNotification.md for the
daily-notification-plugin repo: config shape from the app, expected
behavior, and acceptance criteria so iOS can implement or fix
scheduleDualNotification (currently returns UNIMPLEMENTED).
- PushNotificationPermission: show "Turn on New Activity Notifications"
when enabling New Activity; use NOTIFY_PUSH_SUCCESS_NEW_ACTIVITY for
success toast so copy says "New Activity notifications are now enabled."
- App.vue: on native, turnOffNotifications invokes the modal's callback
only (fixes turn-off not updating state); add comment that callback is
per notification type.
- AccountViewView: handle plugin UNIMPLEMENTED for scheduleDualNotification
on iOS with friendlier message; add New Activity time block and "Edit
New Activity Notification…"; rename Daily Reminder button to "Edit Daily
Reminder…".
- Constants: add NOTIFY_PUSH_SUCCESS_NEW_ACTIVITY. Reminder IDs and
Option A (skip single reminder for New Activity) from earlier commit.
- PushNotificationPermission: on native, when enabling New Activity
(DAILY_CHECK_TITLE), skip scheduleDailyNotification so only
AccountViewView's scheduleNewActivityDualNotification runs (dual
schedule only). Daily Reminder still uses single reminder path.
- Add reminderIds.ts with REMINDER_ID_DAILY_REMINDER and
REMINDER_ID_NEW_ACTIVITY; NativeNotificationService uses the former.
- Export reminder IDs from notifications index. Fixes "always fires /
can't turn off" by avoiding a second, uncancellable single reminder
for New Activity.
Document how daily-notification-plugin aligns with app usage (APIs,
dual vs single schedules, native fetcher, exact alarm). Note attention
items: cancelDailyReminder argument shape, INTEGRATION_GUIDE scope, and
iOS use of app-provided id for scheduleDailyNotification.
Expand notification-new-activity-lay-of-the-land.md with section 7 on
testing New Activity on real iOS/Android devices (prerequisites, enable/
disable flows, what to verify before and after fix). Update Android
device notes to state this app has exact alarm disabled (no
SCHEDULE_EXACT_ALARM) and that delivery may be inexact or batched.
Add section 6 with Option A (skip single reminder when dialog is for New
Activity), Option B (cancel single reminder on disable, with caveats),
and optional cleanup notes. For team discussion and implementation.