feat(android): add fetch scheduling debug logs and triggerImmediateFetch API

- Add DN|SCHEDULE_CALLBACK logs to diagnose fetch scheduling
- Add DN|SCHEDULE_FETCH_* structured logs for traceability
- Add triggerImmediateFetch() public API for standalone fetches
- Update fetch timing from 1 hour to 5 minutes before notification
- Fix TypeScript lint errors: add return types, replace any types
- Fix ESLint warnings: add console suppression comments
- Fix capacitor.settings.gradle plugin path reference
- Update android-app-improvement-plan.md with current state

Changes:
- DailyNotificationPlugin: Added scheduled callback logging and fetch method
- DailyNotificationFetcher: Changed lead time from 1 hour to 5 minutes
- EnhancedDailyNotificationFetcher: Added ENH|* structured event IDs
- TypeScript services: Fixed lint errors and added proper types
- Test app: Fixed capacitor settings path and TypeScript warnings
This commit is contained in:
Matthew Raymer
2025-10-27 10:14:00 +00:00
parent 14287824dc
commit 66987093f7
14 changed files with 341 additions and 130 deletions

View File

@@ -3,4 +3,4 @@ include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':timesafari-daily-notification-plugin'
project(':timesafari-daily-notification-plugin').projectDir = new File('../node_modules/@timesafari/daily-notification-plugin/android/plugin')
project(':timesafari-daily-notification-plugin').projectDir = new File('../node_modules/@timesafari/daily-notification-plugin/android')

View File

@@ -176,6 +176,7 @@ export class TestUserZeroAPI {
if (TEST_USER_ZERO_CONFIG.testing.enableMockResponses) {
// Return mock data for offline testing
console.log("🧪 Using mock starred projects response");
return MOCK_STARRED_PROJECTS_RESPONSE;
}
@@ -194,8 +195,10 @@ export class TestUserZeroAPI {
};
console.log("🌐 Making real API call to:", url);
console.log("📦 Request body:", requestBody);
const response = await fetch(url, {
@@ -217,6 +220,7 @@ export class TestUserZeroAPI {
refreshToken(): void {
this.jwt = generateTestJWT();
console.log("🔄 JWT token refreshed");
}

View File

@@ -165,7 +165,13 @@ export class DiagnosticsExporter {
/**
* Collect system information
*/
private collectSystemInfo() {
private collectSystemInfo(): {
screenResolution: string
colorDepth: number
pixelRatio: number
viewportSize: string
devicePixelRatio: number
} {
return {
screenResolution: `${screen.width}x${screen.height}`,
colorDepth: screen.colorDepth,
@@ -178,7 +184,12 @@ export class DiagnosticsExporter {
/**
* Collect network information
*/
private collectNetworkInfo() {
private collectNetworkInfo(): {
connectionType: string
effectiveType?: string
downlink?: number
rtt?: number
} {
const connection = (navigator as Navigator & { connection?: unknown }).connection ||
(navigator as Navigator & { mozConnection?: unknown }).mozConnection ||
(navigator as Navigator & { webkitConnection?: unknown }).webkitConnection
@@ -194,7 +205,12 @@ export class DiagnosticsExporter {
/**
* Collect storage information
*/
private collectStorageInfo() {
private collectStorageInfo(): {
localStorageAvailable: boolean
sessionStorageAvailable: boolean
indexedDBAvailable: boolean
webSQLAvailable: boolean
} {
return {
localStorageAvailable: this.isStorageAvailable('localStorage'),
sessionStorageAvailable: this.isStorageAvailable('sessionStorage'),

View File

@@ -125,10 +125,12 @@ export class ErrorHandler {
/**
* Log error with context
*/
logError(error: unknown, context = 'DailyNotification') {
logError(error: unknown, context = 'DailyNotification'): void {
console.error(`[${context}] Error:`, error)
if ((error as { stack?: string })?.stack) {
console.error(`[${context}] Stack:`, (error as { stack: string }).stack)
}
}

View File

@@ -170,7 +170,14 @@ export class SchemaValidator {
/**
* Create canonical error response
*/
createErrorResponse(code: ErrorCode, message: string, hint?: string) {
createErrorResponse(code: ErrorCode, message: string, hint?: string): {
success: false
error: {
code: ErrorCode
message: string
hint?: string
}
} {
return {
success: false,
error: {
@@ -184,7 +191,10 @@ export class SchemaValidator {
/**
* Create success response
*/
createSuccessResponse(data?: Record<string, unknown>) {
createSuccessResponse(data?: Record<string, unknown>): {
success: true
[key: string]: unknown
} {
return {
success: true,
...data

View File

@@ -16,7 +16,7 @@ const router = createRouter({
{
path: '/schedule',
name: 'Schedule',
component: () => import('../views/ScheduleView.vue'),
component: (): Promise<typeof import('../views/ScheduleView.vue')> => import('../views/ScheduleView.vue'),
meta: {
title: 'Schedule Notification',
requiresAuth: false
@@ -25,7 +25,7 @@ const router = createRouter({
{
path: '/notifications',
name: 'Notifications',
component: () => import('../views/NotificationsView.vue'),
component: (): Promise<typeof import('../views/NotificationsView.vue')> => import('../views/NotificationsView.vue'),
meta: {
title: 'Notification Management',
requiresAuth: false
@@ -34,7 +34,7 @@ const router = createRouter({
{
path: '/status',
name: 'Status',
component: () => import('../views/StatusView.vue'),
component: (): Promise<typeof import('../views/StatusView.vue')> => import('../views/StatusView.vue'),
meta: {
title: 'System Status',
requiresAuth: false
@@ -43,7 +43,7 @@ const router = createRouter({
{
path: '/user-zero',
name: 'UserZero',
component: () => import('../views/UserZeroView.vue'),
component: (): Promise<typeof import('../views/UserZeroView.vue')> => import('../views/UserZeroView.vue'),
meta: {
title: 'User Zero Testing',
requiresAuth: false
@@ -52,7 +52,7 @@ const router = createRouter({
{
path: '/history',
name: 'History',
component: () => import('../views/HistoryView.vue'),
component: (): Promise<typeof import('../views/HistoryView.vue')> => import('../views/HistoryView.vue'),
meta: {
title: 'Notification History',
requiresAuth: false
@@ -61,7 +61,7 @@ const router = createRouter({
{
path: '/logs',
name: 'Logs',
component: () => import('../views/LogsView.vue'),
component: (): Promise<typeof import('../views/LogsView.vue')> => import('../views/LogsView.vue'),
meta: {
title: 'System Logs',
requiresAuth: false
@@ -70,7 +70,7 @@ const router = createRouter({
{
path: '/settings',
name: 'Settings',
component: () => import('../views/SettingsView.vue'),
component: (): Promise<typeof import('../views/SettingsView.vue')> => import('../views/SettingsView.vue'),
meta: {
title: 'Settings',
requiresAuth: false
@@ -79,7 +79,7 @@ const router = createRouter({
{
path: '/about',
name: 'About',
component: () => import('../views/AboutView.vue'),
component: (): Promise<typeof import('../views/AboutView.vue')> => import('../views/AboutView.vue'),
meta: {
title: 'About',
requiresAuth: false
@@ -88,7 +88,7 @@ const router = createRouter({
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('../views/NotFoundView.vue'),
component: (): Promise<typeof import('../views/NotFoundView.vue')> => import('../views/NotFoundView.vue'),
meta: {
title: 'Page Not Found',
requiresAuth: false
@@ -105,6 +105,7 @@ router.beforeEach((to, from, next) => {
}
// Add loading state
console.log(`🔄 Navigating from ${String(from.name) || 'unknown'} to ${String(to.name) || 'unknown'}`)
next()
@@ -112,6 +113,7 @@ router.beforeEach((to, from, next) => {
router.afterEach((to) => {
// Clear any previous errors on successful navigation
console.log(`✅ Navigation completed: ${String(to.name) || 'unknown'}`)
})

View File

@@ -4,7 +4,7 @@ import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
function increment(): void {
count.value++
}