10 KiB
Issue #810 Analysis: Agenda View Not Showing Overdue Tasks
Problem Understanding
Issue Title: [Bug]: Agenda not showing overdue tasks
Reporter: skyrunner15
Description: When clicking on "today" in the agenda view, it properly filters out prior days but overdue tasks disappear. The user can see the missing task in the Tasks view with a due date of yesterday but not showing in the agenda view. When updating the due date to today, it doesn't show in the agenda view either. The task is a recurring one.
Key Observations:
- Overdue tasks disappear when viewing "today" in the agenda view
- Tasks view shows the overdue task correctly (due date = yesterday)
- Updating task due date to today doesn't make it appear
- The task is recurring, which may be relevant to the bug
Test File Location
A test file has been created but is currently failing:
- Path:
tests/unit/issues/issue-810-overdue-tasks-disappear.test.ts - Test Framework: Jest
- Current Status: ❌ FAILING (missing test helper functions)
- Run Command:
npm test -- issue-810-overdue-tasks-disappear.test.ts
Test Failure Reason:
The test is trying to use createMockCacheManager() and createMockTaskInfo() helper functions which don't exist in the test helpers. The test needs to be updated to use the existing factory methods from tests/helpers/mock-factories.ts:
- Use
TaskFactory.createTask()instead ofcreateMockTaskInfo() - Use
PluginFactory.createMockPlugin().cacheManagerinstead ofcreateMockCacheManager()
Relevant Code Locations
1. AgendaView Component
File: src/views/AgendaView.ts
Key Lines:
AgendaView.ts:731-735- CallsgetAgendaDataWithOverdue()methodAgendaView.ts:843-1018- Renders overdue section separatelyAgendaView.ts:65-showOverdueOnTodayproperty controls overdue display
2. FilterService - Main Logic
File: src/services/FilterService.ts
Key Functions:
a. getAgendaDataWithOverdue()
- Location:
FilterService.ts:2622-2652 - Purpose: Gets agenda data with a separate overdue section
- Parameters:
dates: Date[]- Array of dates to get tasks forbaseQuery: FilterQuery- Filter query to applyshowOverdueSection: boolean- Whether to show overdue section
- Logic:
// Line 2630-2642: Gets tasks for each specific date (no overdue mixing) for (const date of dates) { const tasksForDate = await this.getTasksForDate( date, baseQuery, false // Never include overdue in daily sections ); dailyData.push({ date: new Date(date), tasks: tasksForDate }); } // Line 2645-2646: Gets overdue tasks separately if requested const overdueTasks = showOverdueSection ? await this.getOverdueTasks(baseQuery) : [];
b. getOverdueTasks()
- Location:
FilterService.ts:2573-2617 - Purpose: Get overdue tasks for the agenda view
- Logic:
- Line 2579: Filters all tasks to find overdue ones
- Line 2584-2591: For recurring tasks, only checks
scheduleddate - Line 2593-2606: For non-recurring tasks, checks both
dueandscheduleddates - Line 2588, 2596, 2603: Uses
isOverdueTimeAware()helper function
c. isOverdueTimeAware()
- Location:
src/utils/dateUtils.ts:1021-1050 - Purpose: Check if a date string represents an overdue date
- Parameters:
dateString: string- The date to checkisCompleted?: boolean- Whether the task is completedhideCompletedFromOverdue?: boolean- Setting to hide completed tasks from overdue
- Logic:
- Line 1029-1031: If setting enabled and task completed, return false
- Line 1037-1044: Uses UTC anchors to compare dates consistently
Root Cause Analysis
Based on the code review, the issue appears to be in how recurring tasks are handled in the getOverdueTasks() method:
For Recurring Tasks (Line 2584-2591):
if (task.recurrence) {
// Only check scheduled date for recurring tasks (this is the current instance date)
if (task.scheduled) {
return isOverdueTimeAware(task.scheduled, isCompleted, hideCompletedFromOverdue);
}
return false;
}
Problem: If a recurring task's scheduled date has been updated to today (or is missing), it won't appear as overdue even if previous instances were overdue. The comment says "this is the current instance date" which suggests the scheduled date represents the next/current occurrence, not a past overdue occurrence.
User's Scenario:
- User has a recurring task with scheduled date = yesterday
- User clicks "Today" in agenda view
- The task's scheduled date (yesterday) is checked by
isOverdueTimeAware() - The task is correctly identified as overdue
- BUT when the user updates the due date to today, the task still doesn't show because:
- The overdue logic only checks
scheduledfor recurring tasks - If the user updated
duebut notscheduled, the task won't match either daily or overdue sections
- The overdue logic only checks
Proposed Solutions
Solution 1: Fix Recurring Task Overdue Detection (Recommended)
Approach: Improve the logic in getOverdueTasks() to handle recurring tasks more accurately.
Changes Required:
- File:
src/services/FilterService.ts:2584-2591
Implementation:
if (task.recurrence) {
// For recurring tasks, check scheduled date (current instance)
// Also check due date if it exists (user may set both)
if (task.due) {
if (isOverdueTimeAware(task.due, isCompleted, hideCompletedFromOverdue)) {
return true;
}
}
if (task.scheduled) {
if (isOverdueTimeAware(task.scheduled, isCompleted, hideCompletedFromOverdue)) {
return true;
}
}
return false;
}
Pros:
- ✅ Handles both
dueandscheduleddates for recurring tasks - ✅ Minimal code changes
- ✅ Consistent with non-recurring task logic
- ✅ Fixes the reported issue
Cons:
- ⚠️ May show tasks as overdue that shouldn't be if both dates are set inconsistently
- ⚠️ Need to verify this doesn't break existing recurring task behavior
Solution 2: Separate Overdue Logic for Recurring vs Non-Recurring
Approach: Create distinct overdue detection logic based on task type.
Changes Required:
- File:
src/services/FilterService.ts:2573-2617 - New method:
isRecurringTaskOverdue(task: TaskInfo): boolean
Implementation:
private isRecurringTaskOverdue(task: TaskInfo, isCompleted: boolean, hideCompletedFromOverdue: boolean): boolean {
// For recurring tasks, we need to check if the current instance is overdue
// The scheduled date represents the current/next occurrence
// Check if there's an uncompleted instance in the past
if (task.scheduled) {
const scheduledDate = parseDateToUTC(task.scheduled);
const today = parseDateToUTC(getTodayString());
if (isBefore(scheduledDate, today) && !isCompleted) {
return !hideCompletedFromOverdue || !isCompleted;
}
}
// Also check due date if the recurring task has one set
if (task.due) {
return isOverdueTimeAware(task.due, isCompleted, hideCompletedFromOverdue);
}
return false;
}
// Then use it in getOverdueTasks:
if (task.recurrence) {
return this.isRecurringTaskOverdue(task, isCompleted, hideCompletedFromOverdue);
}
Pros:
- ✅ Clear separation of concerns
- ✅ Easier to test and maintain
- ✅ Can handle complex recurring task scenarios
Cons:
- ⚠️ More code to maintain
- ⚠️ Need thorough testing for edge cases
- ⚠️ Requires understanding of recurring task lifecycle
Solution 3: Add Explicit Overdue Instance Tracking
Approach: Track overdue instances separately in the task metadata.
Changes Required:
- File:
src/types.ts- Addoverdue_instances?: string[]field - File:
src/services/FilterService.ts- Update recurring task logic - File:
src/views/AgendaView.ts- Display overdue instances
Implementation:
// In types.ts
export interface TaskInfo {
// ... existing fields
complete_instances?: string[];
overdue_instances?: string[]; // NEW: Track overdue instances
}
// In getOverdueTasks
if (task.recurrence) {
// Check if current instance is overdue OR if there are tracked overdue instances
const hasOverdueInstances = task.overdue_instances && task.overdue_instances.length > 0;
if (hasOverdueInstances) {
return true;
}
// Check current scheduled date
if (task.scheduled) {
return isOverdueTimeAware(task.scheduled, isCompleted, hideCompletedFromOverdue);
}
return false;
}
Pros:
- ✅ Most accurate tracking of overdue recurring tasks
- ✅ Maintains history of overdue instances
- ✅ Future-proof for complex scenarios
Cons:
- ❌ Requires schema changes
- ❌ Need migration for existing tasks
- ❌ More complex implementation
- ❌ Higher risk of breaking existing functionality
Recommended Approach
Solution 1 is recommended for the following reasons:
- Minimal Risk: Only changes one small section of code
- Fixes the Reported Issue: Handles the user's exact scenario (recurring task with due date)
- Low Complexity: Easy to understand and maintain
- Quick to Implement: Can be done in a single PR
- Easy to Test: Can verify with the existing test file (once helper functions are fixed)
Next Steps
- ✅ Fix the test file - Update to use correct factory methods
- ✅ Verify test fails - Confirm the test reproduces the bug
- ✅ Implement Solution 1 - Make the code changes
- ✅ Verify test passes - Confirm the fix works
- ✅ Manual testing - Test in the actual plugin
- ✅ Edge case testing - Test various recurring task scenarios
Additional Notes
- The codebase uses UTC anchors for date comparisons to avoid timezone issues
- The
hideCompletedFromOverduesetting (default: true) hides completed tasks from overdue section - Recurring tasks use the
scheduledfield to track the current instance date - The
complete_instancesarray tracks which instances of a recurring task have been completed
Related Code Patterns
The codebase follows these patterns for date handling:
- Use
parseDateToUTC()for internal logic and comparisons - Use
parseDateToLocal()for UI display - Use
isOverdueTimeAware(),isSameDateSafe(),isBeforeDateSafe()for safe date comparisons - Store dates in
YYYY-MM-DDformat usingformatDateForStorage()