Skip to Content
AgentsTroubleshooting

Dashboard Troubleshooting Guide

Diagnostic guide for common dashboard issues and their solutions.

Table of Contents

  1. Incorrect Overdue Habits
  2. Next Habit Not Calculating Correctly
  3. Missing Weekly Statistics
  4. Per-Day Counts Showing Zeros
  5. Configuration Not Saving
  6. Platform-Specific Configuration Issues
  7. Dashboard Endpoint Returns 401
  8. Dashboard Endpoint Returns 400
  9. Slow Dashboard Performance
  10. Next Task Not Appearing

Incorrect Overdue Habits

Symptoms

  • Habits showing as overdue when they shouldn’t be
  • Habits not showing as overdue when they should be
  • Incorrect count in overdueHabits field

Common Causes

1. Missing or Invalid dateTime Field

Diagnosis: Check the habit’s dateTime in database:

SELECT id, name, frequency, date_time, done FROM habits WHERE user_id = ?;

Solution:

  • Ensure date_time is not NULL
  • Ensure format is valid ISO 8601: YYYY-MM-DDTHH:mm:ss
  • If missing, update habit:
UPDATE habits SET date_time = '2024-01-16T09:00:00' WHERE id = ?;

2. Incorrect Frequency Type

Diagnosis: Check debug logs:

=== OVERDUE HABITS DEBUG === Checking: Morning run (DALY) - Base: 2024-01-16 07:00 UNKNOWN frequency: DALY

Solution: Fix typo in frequency field:

UPDATE habits SET frequency = 'DAILY' WHERE id = ? AND frequency = 'DALY';

Valid frequencies: DAILY, WEEKLY, MONTHLY, YEARLY (uppercase)

3. Habit Already Marked as Done

Diagnosis: Check debug logs:

Skipping Morning workout - already done

Solution:

  • Verify habit completion status is correct
  • If habit should not be done, reset it:
UPDATE habits SET done = false WHERE id = ?;

4. Timezone Mismatch

Diagnosis: Server and client may be in different timezones, causing time comparison issues.

Check server timezone: Add to getDashboardData:

println("Server timezone: ${TimeZone.currentSystemDefault()}") println("Server time: ${Clock.System.now()}") println("Input time: $currentDateTime")

Solution:

  • Ensure all datetimes are in UTC or same timezone
  • Convert client datetime to server timezone before sending

5. Weekly Habit - Wrong Day Calculation

Symptoms: Weekly habit on Wednesday shows as overdue on Tuesday

Diagnosis: Check debug logs:

WEEKLY: today day of week: 2, target day of week: 3 WEEKLY: target day is in the future this week Final result for Gym workout: false

Solution: This is correct behavior. The habit is scheduled for Wednesday (day 3), and today is Tuesday (day 2), so it’s not overdue yet.

If the behavior is unexpected, verify the baseDate.dayOfWeek is correct:

// In test or debugging val habit = habitService.getHabit(habitId) val baseDateTime = habit.dateTime?.toLocalDateTime() println("Base day of week: ${baseDateTime?.date?.dayOfWeek}") println("Expected day: Wednesday")

6. Monthly Habit - Day of Month Issue

Symptoms: Monthly habit on the 15th shows as overdue on the 10th

Diagnosis: Check if targetDayOfMonth is correctly extracted:

MONTHLY: today is target day, time overdue: false

Solution: Ensure habit dateTime has correct day of month:

-- Check current value SELECT date_time FROM habits WHERE id = ?; -- Should be something like: 2024-01-15T10:00:00 -- Day of month = 15

Debugging Steps

Step 1: Enable verbose logging (already enabled in DashboardService.kt)

Step 2: Make request and check logs:

curl -X GET "http://localhost:8080/api/v1/dashboard?dateTime=2024-01-16T14:30:00" \ -H "Authorization: Bearer $TOKEN"

Step 3: Analyze debug output:

=== OVERDUE HABITS DEBUG === Time: 2024-01-16T14:30:00, Total habits: 3 Today: 2024-01-16, Current time: 14:30 Checking: Morning workout (DAILY) - Base: 2024-01-16 07:00 DAILY: next occurrence: 2024-01-16T07:00, has passed: true Final result for Morning workout: true ...

Step 4: Write unit test to reproduce:

@Test fun `reproduce overdue issue`() { val habit = HabitDTO( id = "test-id", name = "Morning workout", frequency = "DAILY", dateTime = "2024-01-16T07:00:00", done = false, streak = 5 ) val now = "2024-01-16T14:30:00".toLocalDateTime() val overdueHabits = service.getOverdueHabitsList(listOf(habit), now) println("Overdue count: ${overdueHabits.size}") assertEquals(1, overdueHabits.size) // Should be overdue }

Next Habit Not Calculating Correctly

Symptoms

  • nextHabit is null when habits exist
  • Wrong habit returned as “next”
  • Habit with earlier time not prioritized

Common Causes

1. All Habits Are Overdue

Diagnosis: Check debug logs:

Non-overdue habits with occurrence: 0 No non-overdue habits found Selected next habit: null

Explanation: The getNextHabit algorithm filters out overdue habits. If all habits are overdue, it returns null.

Solution:

  • This is expected behavior
  • Complete or reschedule overdue habits
  • Or modify algorithm to return first overdue habit as fallback:
// At end of getNextHabit (line 374-376) if (nonOverdueHabitsWithOccurrence.isEmpty()) { println("No non-overdue habits found, returning first pending habit") return pendingHabits.firstOrNull() // Return any pending habit }

2. All Habits Are Marked as Done

Diagnosis:

Total habits: 5 Pending habits: 0

Solution: Reset completed habits or create new ones:

UPDATE habits SET done = false WHERE user_id = ? AND done = true;

3. Next Occurrence Calculation Error

Diagnosis: Check debug logs for calculation:

Filtering out overdue habit: Meditation (next occurrence: 2024-01-16T18:00)

If time looks correct but still filtered, check current time comparison.

Solution: Verify now parameter is correct:

val currentDateTime = dateTime.toLocalDateTime() println("Current time used: $currentDateTime")

4. Frequency-Specific Calculation Bug

For WEEKLY habits:

// Verify day of week matching val targetDayOfWeek = baseDate.dayOfWeek // e.g., DayOfWeek.WEDNESDAY if (today.dayOfWeek == targetDayOfWeek) today else { /* ... */ }

For MONTHLY habits:

// Verify day of month matching val targetDayOfMonth = baseDate.dayOfMonth // e.g., 15 if (today.dayOfMonth == targetDayOfMonth) today else { /* ... */ }

Debugging Steps

Step 1: Check habit data:

SELECT id, name, frequency, date_time, done, streak FROM habits WHERE user_id = ? ORDER BY date_time;

Step 2: Manually test calculation:

@Test fun `debug next habit calculation`() { val habits = listOf( HabitDTO(id = "1", name = "Morning", frequency = "DAILY", dateTime = "2024-01-16T07:00:00", done = false, streak = 0), HabitDTO(id = "2", name = "Evening", frequency = "DAILY", dateTime = "2024-01-16T20:00:00", done = false, streak = 0) ) val now = "2024-01-16T14:30:00".toLocalDateTime() val result = service.getNextHabit(habits, now) println("Next habit: ${result?.name}") println("Expected: Evening (future time today)") assertNotNull(result) assertEquals("Evening", result?.name) }

Step 3: Check categorization:

Today's habits: 2 (future: 1, past: 1) Future habits: 0 Selected next habit: Evening

Missing Weekly Statistics

Symptoms

  • weeklyTaskCompletion, weeklyHabitCompletion, etc. are 0.0
  • Expected non-zero values

Common Causes

1. No Track Records Exist

Diagnosis: Check track tables:

-- For tasks SELECT COUNT(*) FROM task_tracks WHERE done_date_time >= '2024-01-15 00:00:00' AND done_date_time <= '2024-01-21 23:59:59'; -- For habits SELECT COUNT(*) FROM habit_tracks WHERE done_date_time >= '2024-01-15 00:00:00' AND done_date_time <= '2024-01-21 23:59:59';

Solution: Ensure tasks/habits create track records when completed:

// In TaskService.completeTask taskRepository.update(taskId) { task -> task.done = true } // Create track record taskTrackRepository.create(TaskTrack( taskId = taskId, doneDateTime = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) ))

2. Week Range Calculation Error

Diagnosis: Check week start/end calculation:

// In getDashboardData (line 47-48) val weekStart = today.minus(DatePeriod(days = dayOfWeek - 1)) val weekEnd = weekStart.plus(DatePeriod(days = 6)) println("Week range: $weekStart to $weekEnd")

Solution: Ensure dayOfWeek is 1-7 (Mon-Sun):

val dayOfWeek = today.dayOfWeek.ordinal + 1 // 0-6 → 1-7

3. Track Records Outside Week Range

Diagnosis:

SELECT done_date_time FROM task_tracks ORDER BY done_date_time DESC LIMIT 10;

Check if datetimes fall within the calculated week range.

Solution: Verify timezone consistency when creating track records.

4. Division by Zero (should not happen)

Code: total / 7f (line 182)

This always divides by 7, even if count is 0, resulting in 0.0 (not error).

Debugging Steps

Step 1: Manually query tracks:

SELECT DATE(done_date_time) as day, COUNT(*) as count FROM task_tracks WHERE done_date_time >= '2024-01-15 00:00:00' AND done_date_time <= '2024-01-21 23:59:59' GROUP BY DATE(done_date_time) ORDER BY day;

Step 2: Test calculation function:

@Test fun `weekly completion calculation`() { // Insert mock track records val weekStart = LocalDate(2024, 1, 15) // Monday // Create 3 tracks on Monday, 2 on Tuesday insertTaskTrack(weekStart.atTime(10, 0)) insertTaskTrack(weekStart.atTime(11, 0)) insertTaskTrack(weekStart.atTime(12, 0)) insertTaskTrack(weekStart.plus(DatePeriod(days = 1)).atTime(10, 0)) insertTaskTrack(weekStart.plus(DatePeriod(days = 1)).atTime(11, 0)) val result = service.calculateTaskCompletionFromTracks(weekStart, TaskTracks.doneDateTime, TaskTrack.Companion::find) // Total: 5, divided by 7 = 0.71 assertEquals(0.71f, result, 0.01f) }

Per-Day Counts Showing Zeros

Symptoms

  • tasksCompletedPerDayThisWeek, habitsCompletedPerDayThisWeek are all zeros: [0, 0, 0, 0, 0, 0, 0]

Common Causes

1. Same as “Missing Weekly Statistics”

All causes from previous section apply.

2. Day Range Calculation Error

Diagnosis: Check day boundaries:

val day = weekStart.plus(DatePeriod(days = i)) // i = 0 to 6 val start = day.atTime(0, 0) val end = day.atTime(23, 59) println("Day $i: $start to $end")

Solution: Ensure time range covers full day (00:00 to 23:59).

If you want inclusive range up to midnight:

val end = day.atTime(23, 59, 59) // Include last second

3. Workout Completion Flag Not Set

Specific to workoutsCompletedPerDayThisWeek:

Diagnosis:

SELECT day_of_week, is_completed FROM workout_days WHERE user_id = ?;

Solution: Ensure workouts are marked as completed:

workoutService.completeWorkout(userId, dayOfWeek)

Debugging Steps

Step 1: Check raw data per day:

SELECT DAYOFWEEK(done_date_time) as day_of_week, DATE(done_date_time) as date, COUNT(*) as count FROM task_tracks WHERE done_date_time >= '2024-01-15 00:00:00' AND done_date_time <= '2024-01-21 23:59:59' GROUP BY DATE(done_date_time) ORDER BY date;

Step 2: Test per-day calculation:

@Test fun `per-day counts calculation`() { val weekStart = LocalDate(2024, 1, 15) // Insert 2 tasks on Monday, 3 on Wednesday insertTaskTrack(weekStart.atTime(10, 0)) insertTaskTrack(weekStart.atTime(11, 0)) insertTaskTrack(weekStart.plus(DatePeriod(days = 2)).atTime(10, 0)) insertTaskTrack(weekStart.plus(DatePeriod(days = 2)).atTime(11, 0)) insertTaskTrack(weekStart.plus(DatePeriod(days = 2)).atTime(12, 0)) val result = service.getTrackCountsPerDay(weekStart, TaskTracks.doneDateTime, TaskTrack.Companion::find) assertEquals(listOf(2, 0, 3, 0, 0, 0, 0), result) }

Configuration Not Saving

Symptoms

  • POST /api/v1/dashboard/configuration returns 200 OK
  • But GET /api/v1/dashboard/configuration returns 404 or old data

Common Causes

1. Platform Mismatch

Diagnosis: Check platform in save request vs. get request:

# Save with platform "DESKTOP" curl -X POST .../configuration \ -d '{"configuration":{"platform":"DESKTOP",...}}' # Get with platform "desktop" (lowercase) curl -X GET .../configuration?platform=desktop

Solution: Use exact same platform string (case-sensitive):

curl -X GET .../configuration?platform=DESKTOP

2. Database Transaction Not Committed

Diagnosis: Check service implementation:

fun saveConfiguration(userId: Int, configuration: DashboardConfigurationDTO): Boolean { return transaction { // ... update logic ... true // Return inside transaction block } }

Solution: Ensure transaction is properly committed (Exposed handles this automatically).

3. JSON Serialization Error

Diagnosis: Check server logs for serialization exceptions:

Error serializing configuration: ...

Solution: Ensure all configuration fields are serializable:

@Serializable data class DashboardConfigurationDTO( val widgets: List<DashboardWidgetDTO> = emptyList(), // Provide defaults val gridColumns: Int = 3, val platform: String = "DESKTOP" )

4. Unique Constraint Violation (should be upsert)

Diagnosis: Check for SQL errors:

Unique constraint violation on (user_id, platform)

Solution: Ensure upsert logic (update if exists, insert if not):

transaction { val existing = DashboardConfiguration.find { (DashboardConfigurations.userId eq userId) and (DashboardConfigurations.platform eq configuration.platform) }.firstOrNull() if (existing != null) { existing.configurationJson = Json.encodeToString(configuration) existing.updatedAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) } else { DashboardConfiguration.new { this.userId = userId this.platform = configuration.platform this.configurationJson = Json.encodeToString(configuration) this.createdAt = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) this.updatedAt = this.createdAt } } }

Debugging Steps

Step 1: Check database directly after save:

SELECT user_id, platform, configuration_json, updated_at FROM dashboard_configurations WHERE user_id = ? ORDER BY updated_at DESC;

Step 2: Test save and retrieve:

@Test fun `save and retrieve configuration`() { val config = DashboardConfigurationDTO( widgets = listOf(/* ... */), gridColumns = 3, platform = "DESKTOP" ) val saved = repository.saveConfiguration(userId, config) assertTrue(saved) val retrieved = repository.getConfiguration(userId, "DESKTOP") assertNotNull(retrieved) assertEquals(3, retrieved.gridColumns) }

Platform-Specific Configuration Issues

Symptoms

  • Configuration works on DESKTOP but not on ANDROID
  • Different platforms show same widgets

Common Causes

1. Not Saving Platform-Specific Config

Solution: Save separate configurations per platform:

// Save for desktop val desktopConfig = DashboardConfigurationDTO( widgets = listOf(/* desktop widgets */), platform = "DESKTOP" ) api.saveConfiguration(desktopConfig) // Save for Android val androidConfig = DashboardConfigurationDTO( widgets = listOf(/* mobile-friendly widgets */), platform = "ANDROID" ) api.saveConfiguration(androidConfig)

2. Widget Platform Filter

Solution: Ensure widgets specify which platforms they support:

DashboardWidgetDTO( id = "finance", type = "FINANCE_OVERVIEW", title = "Finances", platforms = listOf("DESKTOP", "WEB"), // Not on mobile // ... )

Frontend should filter:

val platformWidgets = config.widgets.filter { widget -> widget.platforms.contains(currentPlatform) || widget.platforms.isEmpty() }

Dashboard Endpoint Returns 401

Symptoms

HTTP 401 Unauthorized "User not authenticated"

Common Causes

1. Missing Authorization Header

Solution: Include Bearer token:

curl -X GET .../dashboard?dateTime=... \ -H "Authorization: Bearer YOUR_JWT_TOKEN"

2. Invalid or Expired Token

Diagnosis: Decode JWT token:

# Using jwt.io or jwt-cli jwt decode YOUR_TOKEN

Check:

  • exp (expiration) is in future
  • userId field exists

Solution: Re-authenticate to get fresh token:

curl -X POST .../auth/login \ -d '{"username":"user","password":"pass"}'

3. Token Missing userId

Diagnosis: Check JWT payload:

{ "username": "john", "exp": 1234567890 // Missing: "userId" or "id" }

Solution: Ensure token generation includes userId:

val token = JWT.create() .withClaim("userId", user.id) .withExpiresAt(Date(System.currentTimeMillis() + validityMs)) .sign(algorithm)

Dashboard Endpoint Returns 400

Symptoms

HTTP 400 Bad Request "Date parameter is required"

or

"Invalid date time format"

Common Causes

1. Missing dateTime Query Parameter

Solution: Include dateTime in URL:

curl -X GET "http://localhost:8080/api/v1/dashboard?dateTime=2024-01-16T14:30:00" \ -H "Authorization: Bearer TOKEN"

2. Invalid DateTime Format

Invalid formats:

  • 2024-01-16 (missing time)
  • 2024-01-16 14:30:00 (space instead of T)
  • 01/16/2024 14:30 (wrong format)

Valid format:

YYYY-MM-DDTHH:mm:ss 2024-01-16T14:30:00

Solution: Use ISO 8601 format:

// JavaScript const dateTime = new Date().toISOString().slice(0, 19); // "2024-01-16T14:30:00" // Kotlin val dateTime = Clock.System.now() .toLocalDateTime(TimeZone.currentSystemDefault()) .toString()

Slow Dashboard Performance

Symptoms

  • Dashboard endpoint takes > 2 seconds
  • Timeout errors

Common Causes & Solutions

1. No Caching

See Optimizing Dashboard Query Performance in Common Tasks guide.

2. Too Many Database Queries

Diagnosis: Enable SQL logging in Exposed:

addLogger(StdOutSqlLogger)

Count queries in logs.

Solution:

  • Use eager loading with joins
  • Batch queries where possible
  • Add database indexes

3. Large Dataset

Diagnosis: Check row counts:

SELECT COUNT(*) FROM tasks WHERE user_id = ?; SELECT COUNT(*) FROM habits WHERE user_id = ?; SELECT COUNT(*) FROM task_tracks WHERE done_date_time >= ...;

Solution:

  • Add pagination (reduce limit from 100 to 20)
  • Add date range filters
  • Archive old data

4. N+1 Query Problem

Diagnosis: Logs show repeated similar queries:

SELECT * FROM tasks WHERE id = 1 SELECT * FROM tasks WHERE id = 2 SELECT * FROM tasks WHERE id = 3 ...

Solution: Use batch loading:

// Instead of tasks.forEach { task -> val details = taskRepository.getDetails(task.id) } // Use val taskIds = tasks.map { it.id } val allDetails = taskRepository.getBatchDetails(taskIds)

Next Task Not Appearing

Symptoms

  • nextTask is null when tasks exist
  • Completed task still showing as next

Common Causes

1. All Tasks Are Completed

Diagnosis:

SELECT COUNT(*) as total, SUM(CASE WHEN done = true THEN 1 ELSE 0 END) as completed FROM tasks WHERE user_id = ?;

Solution: Create new tasks or unmark completed ones.

2. All Tasks Are Future-Dated

Diagnosis:

SELECT title, due_date_time, scheduled_date_time FROM tasks WHERE user_id = ? AND done = false ORDER BY COALESCE(due_date_time, scheduled_date_time);

All dates > today?

Solution: This is expected. getNextTask will return the highest priority future task.

3. Tasks Missing Due/Scheduled DateTime

Diagnosis:

SELECT COUNT(*) FROM tasks WHERE user_id = ? AND done = false AND due_date_time IS NULL AND scheduled_date_time IS NULL;

Solution: Algorithm requires either dueDateTime or scheduledDateTime. Add dates to tasks:

UPDATE tasks SET scheduled_date_time = NOW() WHERE id = ? AND due_date_time IS NULL AND scheduled_date_time IS NULL;

4. Priority Sorting Issue

Diagnosis: Check task priorities:

SELECT title, priority, due_date_time FROM tasks WHERE user_id = ? AND done = false ORDER BY priority DESC;

Solution: Ensure tasks have meaningful priorities (1-5).


General Debugging Checklist

When encountering any dashboard issue:

  1. Check server logs - Look for debug output and exceptions
  2. Verify authentication - Ensure valid JWT token with userId
  3. Validate input - DateTime format, platform string, etc.
  4. Check database - Query relevant tables directly
  5. Test in isolation - Create unit test reproducing the issue
  6. Enable verbose logging - Add println statements if needed
  7. Check timezone consistency - Server and client should align
  8. Verify data integrity - Non-null required fields, valid enum values
  9. Test with cURL - Eliminate frontend issues
  10. Review recent changes - Did recent code changes break something?