feat: Add setting to hide completed tasks from overdue status (#300)

Implements a new "Hide completed tasks from overdue" setting in the misc
section that controls whether completed tasks appear as overdue in
agenda
views. When enabled (default), completed tasks will not be classified as
overdue even if their due or scheduled date has passed.

Changes:
- Add `hideCompletedFromOverdue` setting (default: true) to settings
interface
- Enhance `isOverdueTimeAware` function with completion status
parameters
- Update FilterService constructor to accept plugin reference for
settings access
- Add completion-aware overdue detection throughout date categorization
- Update all FilterService instantiations with new constructor signature
- Add comprehensive documentation in misc-settings.md
- Create test documentation for the new feature
- Update NotesView empty state message for better clarity

This change helps keep overdue lists focused on actionable items while
maintaining proper task categorization for completed work.
This commit is contained in:
Callum Alpass 2025-07-27 10:20:28 +10:00
parent ec54a80026
commit b01400f852
10 changed files with 151 additions and 15 deletions

View file

@ -10,6 +10,10 @@ These settings control various plugin features and display options that don't fi
**Show project subtasks widget** - Display a collapsible widget showing all tasks that reference the current note as a project. This widget appears in the editor when viewing notes that have tasks referencing them as projects, providing quick access to related tasks.
## Task Display
**Hide completed tasks from overdue** - Control whether completed tasks appear as overdue in the agenda view. When enabled (default), completed tasks will not appear in the "Overdue" section of the agenda view, even if their due or scheduled date has passed. This setting affects task grouping and overdue detection throughout the plugin, helping keep overdue lists focused on actionable items.
## Performance Settings
**Disable note indexing** - Disable indexing and caching of non-task notes to improve performance in large vaults. Note: This will disable the Notes view and notes display in the Agenda view. Requires plugin restart to take effect.

View file

@ -25,7 +25,8 @@ class ProjectSubtasksWidget extends WidgetType {
this.filterService = new FilterService(
plugin.cacheManager,
plugin.statusManager,
plugin.priorityManager
plugin.priorityManager,
plugin
);
// Initialize with default filter query

View file

@ -185,7 +185,8 @@ export default class TaskNotesPlugin extends Plugin {
this.filterService = new FilterService(
this.cacheManager,
this.statusManager,
this.priorityManager
this.priorityManager,
this
);
this.viewStateManager = new ViewStateManager(this.app, this);
this.projectSubtasksService = new ProjectSubtasksService(this);

View file

@ -42,7 +42,8 @@ export class FilterService extends EventEmitter {
constructor(
cacheManager: MinimalNativeCache,
statusManager: StatusManager,
priorityManager: PriorityManager
priorityManager: PriorityManager,
private plugin?: any // Plugin reference for accessing settings
) {
super();
this.cacheManager = cacheManager;
@ -769,25 +770,28 @@ export class FilterService extends EventEmitter {
const referenceDate = targetDate || new Date();
referenceDate.setHours(0, 0, 0, 0);
const isCompleted = this.statusManager.isCompletedStatus(task.status);
const hideCompletedFromOverdue = this.plugin?.settings?.hideCompletedFromOverdue ?? true;
// For recurring tasks, check if due on the target date
if (task.recurrence) {
if (isDueByRRule(task, referenceDate)) {
// If due on target date, determine which group based on target date vs today
const referenceDateStr = format(referenceDate, 'yyyy-MM-dd');
return this.getDateGroupFromDateString(referenceDateStr);
return this.getDateGroupFromDateStringWithTask(referenceDateStr, isCompleted, hideCompletedFromOverdue);
} else {
// Recurring task not due on target date
// If it has an original due date, use that, otherwise no due date
if (task.due) {
return this.getDueDateGroupFromDate(task.due);
return this.getDateGroupFromDateStringWithTask(task.due, isCompleted, hideCompletedFromOverdue);
}
return 'No due date';
}
}
// Non-recurring task - use original logic
// Non-recurring task - use completion-aware logic
if (!task.due) return 'No due date';
return this.getDueDateGroupFromDate(task.due);
return this.getDateGroupFromDateStringWithTask(task.due, isCompleted, hideCompletedFromOverdue);
}
/**
@ -797,7 +801,9 @@ export class FilterService extends EventEmitter {
private getDateGroupFromDateString(dateString: string): string {
const todayStr = getTodayString();
// Use time-aware overdue detection
// Use time-aware overdue detection with completion-aware logic
// For categorization purposes, we need the task to determine completion status
// This call is for categorization only, specific task overdue checks happen elsewhere
if (isOverdueTimeAware(dateString)) return 'Overdue';
// Extract date part for day-level comparisons
@ -829,9 +835,87 @@ export class FilterService extends EventEmitter {
return this.getDateGroupFromDateString(dueDate);
}
/**
* Helper method to get due date group for a specific task (completion-aware)
*/
private getDueDateGroupForTask(task: TaskInfo): string {
if (!task.due) return 'No due date';
const isCompleted = this.statusManager.isCompletedStatus(task.status);
const hideCompletedFromOverdue = this.plugin?.settings?.hideCompletedFromOverdue ?? true;
return this.getDateGroupFromDateStringWithTask(task.due, isCompleted, hideCompletedFromOverdue);
}
/**
* Get date group from date string with task completion awareness
*/
private getDateGroupFromDateStringWithTask(dateString: string, isCompleted: boolean, hideCompletedFromOverdue: boolean): string {
const todayStr = getTodayString();
// Use completion-aware overdue detection
if (isOverdueTimeAware(dateString, isCompleted, hideCompletedFromOverdue)) return 'Overdue';
// Extract date part for day-level comparisons
const datePart = getDatePart(dateString);
if (isSameDateSafe(datePart, todayStr)) return 'Today';
try {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const tomorrowStr = format(tomorrow, 'yyyy-MM-dd');
if (isSameDateSafe(datePart, tomorrowStr)) return 'Tomorrow';
const thisWeek = new Date();
thisWeek.setDate(thisWeek.getDate() + 7);
const thisWeekStr = format(thisWeek, 'yyyy-MM-dd');
if (isBeforeDateSafe(datePart, thisWeekStr) || isSameDateSafe(datePart, thisWeekStr)) return 'This week';
return 'Later';
} catch (error) {
console.error(`Error categorizing date ${dateString}:`, error);
return 'Invalid Date';
}
}
private getScheduledDateGroup(task: TaskInfo, targetDate?: Date): string {
if (!task.scheduled) return 'No scheduled date';
return this.getScheduledDateGroupFromDate(task.scheduled);
const isCompleted = this.statusManager.isCompletedStatus(task.status);
const hideCompletedFromOverdue = this.plugin?.settings?.hideCompletedFromOverdue ?? true;
return this.getScheduledDateGroupForTask(task.scheduled, isCompleted, hideCompletedFromOverdue);
}
/**
* Get scheduled date group with task completion awareness
*/
private getScheduledDateGroupForTask(scheduledDate: string, isCompleted: boolean, hideCompletedFromOverdue: boolean): string {
const todayStr = getTodayString();
// Use completion-aware overdue detection for past scheduled
if (isOverdueTimeAware(scheduledDate, isCompleted, hideCompletedFromOverdue)) return 'Past scheduled';
// Extract date part for day-level comparisons
const datePart = getDatePart(scheduledDate);
if (isSameDateSafe(datePart, todayStr)) return 'Today';
try {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const tomorrowStr = format(tomorrow, 'yyyy-MM-dd');
if (isSameDateSafe(datePart, tomorrowStr)) return 'Tomorrow';
const thisWeek = new Date();
thisWeek.setDate(thisWeek.getDate() + 7);
const thisWeekStr = format(thisWeek, 'yyyy-MM-dd');
if (isBeforeDateSafe(datePart, thisWeekStr) || isSameDateSafe(datePart, thisWeekStr)) return 'This week';
return 'Later';
} catch (error) {
console.error(`Error categorizing scheduled date ${scheduledDate}:`, error);
return 'Invalid Date';
}
}
/**
@ -1271,16 +1355,19 @@ export class FilterService extends EventEmitter {
// If showing overdue tasks and this is today, include overdue tasks on today
if (includeOverdue && isViewingToday) {
const isCompleted = this.statusManager.isCompletedStatus(task.status);
const hideCompletedFromOverdue = this.plugin?.settings?.hideCompletedFromOverdue ?? true;
// Check if due date is overdue (show on today)
if (task.due && getDatePart(task.due) !== dateStr) {
if (isOverdueTimeAware(task.due)) {
if (isOverdueTimeAware(task.due, isCompleted, hideCompletedFromOverdue)) {
return true;
}
}
// Check if scheduled date is overdue (show on today)
if (task.scheduled && getDatePart(task.scheduled) !== dateStr) {
if (isOverdueTimeAware(task.scheduled)) {
if (isOverdueTimeAware(task.scheduled, isCompleted, hideCompletedFromOverdue)) {
return true;
}
}

View file

@ -56,6 +56,8 @@ export interface TaskNotesSettings {
autoStopTimeTrackingNotification: boolean;
// Project subtasks widget settings
showProjectSubtasks: boolean;
// Overdue behavior settings
hideCompletedFromOverdue: boolean;
// ICS integration settings
icsIntegration: ICSIntegrationSettings;
// Saved filter views
@ -297,6 +299,8 @@ export const DEFAULT_SETTINGS: TaskNotesSettings = {
autoStopTimeTrackingNotification: false,
// Project subtasks widget defaults
showProjectSubtasks: true,
// Overdue behavior defaults
hideCompletedFromOverdue: true,
// ICS integration defaults
icsIntegration: {
defaultNoteTemplate: '',
@ -1514,6 +1518,21 @@ export class TaskNotesSettingTab extends PluginSettingTab {
this.plugin.notifyDataChanged();
});
});
// Hide completed tasks from overdue
new Setting(container)
.setName('Hide completed tasks from overdue')
.setDesc('When enabled, completed tasks will not appear as overdue in the agenda view, even if their due/scheduled date has passed')
.addToggle(toggle => {
toggle.toggleEl.setAttribute('aria-label', 'Hide completed tasks from overdue status');
return toggle
.setValue(this.plugin.settings.hideCompletedFromOverdue)
.onChange(async (value) => {
this.plugin.settings.hideCompletedFromOverdue = value;
await this.plugin.saveSettings();
// Refresh views to apply the change
this.plugin.notifyDataChanged();
});
});
// Notes indexing toggle
new Setting(container)

View file

@ -670,9 +670,14 @@ export function isBeforeDateTimeAware(date1: string, date2: string): boolean {
/**
* Check if a date/datetime is overdue (past current date/time)
*/
export function isOverdueTimeAware(dateString: string): boolean {
export function isOverdueTimeAware(dateString: string, isCompleted?: boolean, hideCompletedFromOverdue?: boolean): boolean {
if (!dateString) return false;
// If the setting is enabled and task is completed, don't consider it overdue
if (hideCompletedFromOverdue && isCompleted) {
return false;
}
try {
const taskDate = parseDate(dateString);
const now = new Date();

View file

@ -194,7 +194,7 @@ export class NotesView extends ItemView {
.setName('No notes found')
.setHeading();
emptyState.createEl('p', {
text: 'No notes found for the selected date. Try selecting a different date or create some notes.',
text: 'No notes found for the selected date. Try selecting a different date in the Mini Calendar view or create some notes.',
cls: 'notes-view__empty-description'
});
}

19
test-overdue-setting.md Normal file
View file

@ -0,0 +1,19 @@
# Test: Hide Completed Tasks from Overdue Setting
## Test Steps
1. Enable the "Hide completed tasks from overdue" setting in Settings > Misc
2. Create a task with a due date in the past (e.g., yesterday)
3. Mark the task as completed
4. Check the agenda view - the completed task should NOT appear as overdue
5. Disable the setting
6. Check the agenda view again - the completed task should now appear as overdue
## Expected Behavior
- When setting is enabled (default): Completed tasks don't show as overdue
- When setting is disabled: Completed tasks show as overdue if their date has passed
## Implementation Details
- Setting: `hideCompletedFromOverdue` (boolean, default: true)
- Function: `isOverdueTimeAware(dateString, isCompleted?, hideCompletedFromOverdue?)`
- Location: Settings > Misc tab
- Impact: Affects agenda view, task grouping, and overdue categorization

View file

@ -46,7 +46,7 @@ describe('FilterService - Issue 153 Fix Verification', () => {
mockPriorityManager = {} as any;
filterService = new FilterService(mockCacheManager, mockStatusManager, mockPriorityManager);
filterService = new FilterService(mockCacheManager, mockStatusManager, mockPriorityManager, null);
});
describe('Timezone-safe date comparisons', () => {

View file

@ -55,7 +55,7 @@ describe('FilterService - Issue 153 Timezone Bug Fix Verification', () => {
} as any;
mockPriorityManager = {} as any;
filterService = new FilterService(mockCacheManager, mockStatusManager, mockPriorityManager);
filterService = new FilterService(mockCacheManager, mockStatusManager, mockPriorityManager, null);
});
describe('Date comparison edge cases that cause issue 153', () => {